【数据库原理】并发控制原理

        事务之间的相互影响可能导致数据库状态的不一致,即使各个事务能保持状态的正确性,而且也没有任何故障发生。因此,不同事务中各个步骤的执行顺序必须以某种方式进行规范。控制这些步骤的功能由DBMS的调度器部件完成,而保证并发执行的事务能保持一致性的整个过程称为并发控制。调度器的作用如图1所示。
 

  •  调度器:负责控制事务的执行顺序,让其顺序的在缓冲区上执行
  •  并发控制:保证并发执行事务能保持一致性

      首先讨论如何保证并发执行的事务能保持数据库状态的正确性。抽象的要求称为可串行性,另外还有一个更强的、重要的条件为冲突可串行性,它是大多数调度器所真正实现的。我们考虑实现调度器的最重要技术:

  • 封锁 
  • 时间戳
  • 有效性确认

串行调度和可串行化调度

1.1 调度

   调度是一个或多个事务的重要操作按时间排序的一个序列。

   考虑两个事务以及它们的动作按照某些顺序执行时的数据库的影响。T1和T2的重要动作如表1所示。  

1.2 串行调度

      如果一个调度的动作首先是一个事务的所有动作,然后是另一个事务的所有动作,以此类推,而没有动作的混合,那么我们说这一调度是串行的。

  对表1中的事务而言,两个串口调度,一个是T1在T2前,而另一个是T2是T1之前,初态为A=B=25。

1.3 可串行化调度

事务的正确性原则告诉我们,每个串行调度都将保持数据库状态的一致性。
   通常,不管数据库初态怎样,一个调度对数据库状态的影响都和某个串行调度相同,我们就说这个调度是可串行化的。
   例3 表4是例1中事务的一个调度,此调度是可串行化的,但不是串行的。表5不是可串行化的。
                                                               表5 一个非串行的可串行化调度

1.4 事务语义的影响

   事务细节确实有关系的,正如我们将在下面的例子中看到的那样。

   表7,T2并非将A和B乘2,而是乘1。现在,在这一调度的结尾,A和B的值相等。

                                      表7 一个仅仅由于事务细节行为而可串行化的调度

遗憾的是,调度器考虑事务所进行的计算的细节是不现实的。但是,调度器的确能看到来自事务的读写请求,于是能够知道每个事务读哪些数据库元素,以及它可能改变哪些元素。为了简化调度器的工作,通常假定:
   1)事务T所写的任意数据元素A被赋予一个值,该值以这样一种方式依赖于数据库状态,即不会发生算术上的巧合。

1.5 事务和调度的一个记法

 我们接受事务所进行的精确计算可以是任意的,那么我们不需要考虑像t := t+100这样的局部计算步骤。只有事务的读和写需要考虑。因此,我们将用一种新的记法来表示事务和调度,其中动作有rT(X)和wT(X)分别表示事务T读和写数据库元素X。
   例5  表1所示的事务可以写为:
        T1:  r1(A); w1(A); r1(B);w1(B);
        T2: r2(A); w2(A); r2(B);w2(B);
   表5中T1和T2的可串行化调度。这一调度写为:
   r1(A); w1(A); r2(A); w2(A); r1(B);w1(B); r2(B);w2(B);
 
为使这一记法更精确:
1)动作是形如ri(X)或wi(X)的表达式,分别表示事务Ti读或写数据元素X。
2)事务Ti是具有下标i的动作序列。
3)事务集合T的调度S是一个动作序列,其中对T中的每个事务Ti,Ti中的动作在S中出现的顺序和其在Ti自身定义出现的顺序一样。我们说S是组成它的事务动作的一个交错。

冲突可串行化

2.1 冲突
 大多数的动作对按上面的理解并不冲突。
 不同事务的任意两个动作在顺序上可以交换,除非:
1)它们涉及同一数据库元素;并且
2)至少有一个是写。
 
   将这一思想进行扩展,我们可以接受任一调度,进行任意非冲突的交换,目标是将该调度转换为一个串行调度。如果我们能做到这一点,那么初始的调度是可穿行化的,因为它对数据库状态的影响在我们做一个非冲突交换时是不变的。
   我们说两个调度是冲突等价的,如果通过一系列相邻的动作的非冲突化交换能将它们中的一个转换为另一个。如果一个调度冲突等价于一个串行调度,那么我们记说该调度是冲突可串行化的。
   例6   考虑例5中的调度
          r1(A); w1(A); r2(A); w2(A); r1(B);w1(B); r2(B);w2(B);
   通过交换相邻动作将冲突可串行化调度转换为串行调度
          r1(A); w1(A); r1(B);w1(B);r2(A); w2(A); r2(B);w2(B);

2.2 优先图及冲突可串行化判断
   已知调度S,其中涉及事务T1和T2,可那个还有其他事务,我们说T1优先于T2,写作T1<ST2,如果有T1的动作A1和T2的动作A2,满足:
1)在S中A1在A2前;
2)A1和A2都涉及同一数据库元素;并且
3)A1和A2至少有一个是写动作。
   这正是我们不能交换A1和A2顺序的情况。因此,在任何冲突等价于S的调度中,A1将出现在A2的前面。所以,如果这些调度中有一个是串行调度,那么该调度必然是T1在T2前。
   我可以使用优先图概括这样的先后次序。优先图中的结点是调度S中的事务。如果Ti<STj,则有一条从结点i到结点j的弧。
 
 例7 下面的调度S涉及三个事务T1、T2和T3。
    S:r2(A);r1(B);w2(A);r3(A);w1(B);w3(A);r2(B);w2(B)



判断调度S是否是冲突可串行化有一条简单的规则:
1)构造S的优先图,并判断其中是否有环。
如果有,那么S不是冲突可串行化的。如果该图是无环的,那么S是冲突可串行化的。
 
例8  考虑调度:
     S1: r2(A);r1(B);w2(A); r2(B);r3(A);w1(B);w3(A); w2(B)

该图中显然有环,我们断定S1不是冲突可串行化的。
 
 


3 使用锁的可串行性实现


   设想以一种不受约束的方式进行其动作的事务的一个集合。这些动作将形成以个调度,但是这一调度不大可能是可串行化的。
   我们考虑调度器最常用的体系结构,这种结构在数据库元素上维护“锁”,以防止非可串行化的行为。
   在本节中,我们用一个(过于)简单的封锁模式来介绍封锁的概念。这种模式中只有一种锁,它是事务想要在数据库元素上执行任何操作时都必须在该数据库元素上获得的。
 
3.1 锁
   在图4中我们看到一个使用锁表来协助自己工作的调度器。

当调度器使用锁时,事务在读写数据库元素以外还必须申请和释放锁。锁的使用必须在两种意义上都是正确的:一种适用于事务的结构,另外一种适用于调度的结构。
 事务的一致性:动作和锁必须按预期的方式发生联系:
      1)事务只有以前已经在数据库元素上申请了锁并且还没有释放锁时才能读或写该数据元素。
      2)如果事务封锁某个数据库元素,它必须为该元素解锁。
·调度的合法性:锁必须具有其预期的含义:任何两个事务都不能封锁同一元素,除非其中一个事务已经先释放其锁。
     扩展我们先前关于动作的记法,并加入封锁和解锁动作:
      li(X):事务Ti请求数据库元素X上的锁。
      ui(X):事务Ti释放它在数据库元素X上的锁(解锁)。
 
 事务的一致性条件可以表述为:

   “只要事务Ti有动作ri(X)或wi(X),那么前面必然有一个动作li(X)且两者之间没有ui(X),并且后面将  会有一 个ui(X)”。                           调度的合法性表述为:

   “如果调度中现有li(X)后有lj(X),那么这些动作之间的某个地方必然有一个动作ui(X)”
 
 例9 考虑例1中介绍的事务T1和T2。
  T1:  l1(A);r1(A); A := A+100;w1(A); u1(A);l1(B);r1(B);B:=B+100;w1(B);u1(B);
   T2: l2(A);r2(A); A := A*2;w2(A); u2(A);l2(B);r2(B);B:=B*2;w2(B);u2(B);
 
                                        表8 一致事务的一个合法调度;但不幸的是,它不是可串行化的

3.2 封锁调度器
  基于封锁的调度器的任务是当且仅当请求将产生合法调度时同意请求。为了帮助进行决策,调度器有一个锁表,对于每一个数据库元素,如果其上有锁,那么锁表明当前持有该锁的事务。我们将在5.2节更详细地讨论锁表的结构。
 
 例10对来自例9的T1和T2稍作修改,其中T1和T2都在释放A上的锁之前封锁B
   T1:  l1(A);r1(A); A := A+100;w1(A); l1(B); u1(A);r1(B);B:=B+100;w1(B);u1(B);
   T2: l2(A);r2(A); A := A*2;w2(A); l2(B); u2(A);r2(B);B:=B*2;w2(B);u2(B);

3.3 两阶段封锁
    在一个另人吃惊的条件下,我们可以保证一致事务的合法调度是冲突可串行化的。这一条件称为两阶段封锁或2PL,在商用封锁系统中被广泛采用。2PL条件是:
   ·在每个事务中,所有封锁请求先于所有解锁请求。
    因此2PL中所指的“两阶段”是获得锁的第一阶段和释放锁的第二阶段。服从2PL条件的事务被称为两阶段封锁事务或2PL事务。

在例9中事务不遵循两阶段封锁规则,比如T1在封锁B以前解锁A。但是在例10中事务时遵循2PL条件的。

我们注意到,T1的前5个动作中封锁了A和B,并且在后5个动作中解锁。T2的行为类似,所以比较两个例子我们能看得出来,2PL时如何和调度器正确交互保证事务的一致性的。

3.4 死锁的风险

2PL封锁为解决的一个问题就是死锁的风险,两个事务先后锁住所需访问对象A,B,然后在进行申请另一个对象的锁时陷入死锁。

死锁的解除有两种方式:

  • 死锁检测
  • 超时回退

具体细节后面讨论

4 有多种锁模式的封锁系统

2PL所阐述的封锁模式过于简单,但并不实用,比如多个并发事务去读对象时,去写对象时,是一个什么样的执行情况,需不需要对锁的概念进行细化。

4.1 共享锁和排他锁

       事务的一致性,事务的2PL和调度的合法性着三类要求在每一个共享/排他的封锁系统中都有各自的对应关系。

4.2 相容性矩阵

相容性矩阵提供锁授予的规则:

4.3 锁的升级

当一个想要读X并写入新值的事务T首先获得X上的一个而共享锁后再去申请X上的一个排他锁,可以将共享锁升级为排他锁

虽然锁的升级能够带来更高的并发性,但也加大了死锁的风险。

T1和T2都能够得到A上的共享锁,接着,他们都试图升级到排他锁,但由于另一个事务在A上持有共享锁,调度器于是迫使他们都进行等待

4.4 更新锁

于是通过引入更新锁的概念来避免由于锁的升级带来的死锁问题。

更新锁是已经持有了X元素上的共享锁之后去申请的。当X上已经有了共享锁我们可以授予X上的更新锁,但一旦X上有了更新锁,则X上禁止加其他任何类的锁。

更新锁解决了锁的4.3中所带来的死锁问题:

5 封锁调度器的体系结构

不同的封锁机制下,调度器的行为不同,但有几个原则必须注意:

事务自身不会去申请封锁,在读,写以及其他访问数据的动作中插入锁的动作是调度器的任务。

事务不释放锁,而是调度器在事务管理器告诉事务将提交或终止时释放锁。

5.1 插入锁动作的调度器

5.2 锁表

锁表是将数据库元素和与该元素有关的封锁信息联系起来的一个关系表。

锁表用一个散列表实现,数据库元素(地址)作为散列码,没有被封锁的元素在表中不出现,表会动态增减。

                                                                        锁表的结构

6 数据库元素的层次

回想第4节中对于不同封锁模式的探索,我们主要关注在数据中存在树结构时出现的问题。

1. 第一类问题是可封锁元素的层次结构。如何才能即允许大的元素如关系上的锁,又允许较小的元素(如容纳关系中元组的块,或者单个的元组)上的锁。

2. 并发控制中另一类重要的层次是本身就组织为一棵树的数据,例如B-树索引,我们将B-树的节点看作数据库元素。

6.1 多粒度锁

数据库元素这个概念可以指,元组,页,以及关系,根据系统的并发要求选择不同粒度的锁。

比如考虑一个文档的数据库,文档可以被编辑,明治的选择应该是将整个文档作为数据库元素,而不是对某一个单独的段落,或者句子。

6.2 警示锁

用来解决管理不同粒度的锁的问题涉及一种新的锁,称为警示,这样的锁在数据库元素形成嵌套或层次结构时很有用。

如下图所示:其中我们能看到三个级别的数据库元素:

6.3 幻象与插入的正确处理

7 使用时间戳的并发控制(MVCC)

理解MVCC需要结合以下几点:

发布了13 篇原创文章 · 获赞 7 · 访问量 377

猜你喜欢

转载自blog.csdn.net/weixin_39966701/article/details/105175561