深入理解数据库并发控制原理

来源:互联网 发布:淘宝助理违规校验错误 编辑:程序博客网 时间:2024/06/06 15:45
并发控制原理

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

  

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

  1.串行调度和可串行化调度

  1.1 调度

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

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

1两个事务
T1
T2
READ(A,t)
READ(A,s)
t := t + 100
s := s*2
WRITE(A,t)
WRITE(A,s)
READ(B,t)
READ(B,s)
t := t + 100
s := s*2
WRTIE(B,t)
WRITE(B,s)

  1.2 串行调度

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

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

2 T1T2前的串行调度

T1
T2
A
B
 
 
25
25
READ(A,t)
 
 
 
t := t + 100
 
 
 
WRITE(A,t)
 
125
 
READ(B,t)
 
 
 
t := t + 100
 
 
 
WRTIE(B,t)
 
 
125
 
READ(A,s)
 
 
 
s := s*2
 
 
 
WRITE(A,s)
250
 
 
READ(B,s)
 
 
 
s := s*2
 
 
 
WRITE(B,s)
 
250
3 T2T1前的串行调度

T1
T2
A
B
 
 
25
25
 
READ(A,t)
 
 
 
t := t + 100
 
 
 
WRITE(A,t)
50
 
 
READ(B,t)
 
 
 
t := t + 100
 
 
 
WRTIE(B,t)
 
50
READ(A,s)
 
 
 
s := s*2
 
 
 
WRITE(A,s)
 
150
 
READ(B,s)
 
 
 
s := s*2
 
 
 
WRITE(B,s)
 
 
150

  1.3 可串行化调度

  事务的正确性原则告诉我们,每个串行调度都将保持数据库状态的一致性。

  通常,不管数据库初态怎样,一个调度对数据库状态的影响都和某个串行调度相同,我们就说这个调度是可串行化的。

  例3 表4是例1中事务的一个调度,此调度是可串行化的,但不是串行的。表5不是可串行化的。

5一个非串行的可串行化调度

T1
T2
A
B
 
 
25
25
READ(A,t)
 
 
 
t := t + 100
 
 
 
WRITE(A,t)
 
125
 
 
READ(A,s)
 
 
 
s := s*2
 
 
 
WRITE(A,s)
250
 
READ(B,t)
 
 
 
t := t + 100
 
 
 
WRTIE(B,t)
 
 
125
 
READ(B,s)
 
 
 
s := s*2
 
 
 
WRITE(B,s)
 
250

 

6一个非可串行化的调度

T1
T2
A
B
 
 
25
25
READ(A,t)
 
 
 
t := t + 100
 
 
 
WRITE(A,t)
 
125
 
 
READ(A,s)
 
 
 
s := s*2
 
 
 
WRITE(A,s)
250
 
 
READ(B,s)
 
 
 
s := s*2
 
 
 
WRITE(B,s)
 
50
READ(B,t)
 
 
 
t := t + 100
 
 
 
WRTIE(B,t)
 
 
150

  1.4 事务语句的影响

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

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

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

T1
T2
A
B
 
 
25
25
READ(A,t)
 
 
 
t := t + 100
 
 
 
WRITE(A,t)
 
125
 
 
READ(A,s)
 
 
 
s := s*1
 
 
 
WRITE(A,s)
125
 
 
READ(B,s)
 
 
 
s := s*1
 
 
 
WRITE(B,s)
 
25
READ(B,t)
 
 
 
t := t + 100
 
 
 
WRTIE(B,t)
 
 
150

  遗憾的是,调度器考虑事务所进行的计算的细节是不现实的。但是,调度器的确能看到来自事务的读写请求,于是能够知道每个事务读哪些数据库元素,以及它可能改变哪些元素。为了简化调度器的工作,通常假定:

  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.冲突可串行化

  我们现在将提出一个足以保证调度可串行化的条件。它基于冲突这一概念:调度中一对连续的动作,它们满足:如果它们的顺序交换,那么涉及的事务中至少有一个的行为会改变。

  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

  1)在S中A1在A2前;

  2)A1和A2都涉及同一数据库元素;并且

  3)A1和A2至少有一个是写动作。

  这正是我们不能交换A1和A2顺序的情况。因此,在任何冲突等价于S的调度中,A1将出现在A2的前面。所以,如果这些调度中有一个是串行调度,那么该调度必然是T1在T2前。

  我可以使用优先图概括这样的先后次序。优先图中的结点是调度S中的事务。如果Ti

  例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一致事务的一个合法调度;但不幸的是,它不是可串行化的

T1
T2
A
B
 
 
25
25
l1(A);r1(A);
 
 
 
A := A+100;
 
 
 
w1(A); u1(A);
 
125
 
 
l2(A);r2(A);
 
 
 
A := A*2;
 
 
 
w2(A); u2(A);
250
 
 
l2(B);r2(B);
 
 
 
B:=B*2;
 
 
 
w2(B);u2(B);
 
50
l1(B);r1(B);
 
 
 
B:=B+100;
 
 
 
w1(B);u1(B);
 
 
150

  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);

T1
T2
A
B
 
 
25
25
l1(A);r1(A);
 
 
 
A := A+100;
 
 
 
w1(A); l1(B); u1(A);
 
125
 
 
l2(A);r2(A);
 
 
 
A := A*2;
 
 
 
w2(A);
250
 
 
l2(B);
 
 
r1(B);B:=B+100;
 
 
 
w1(B);u1(B);
 
 
125
 
l2(B); u2(A);r2(B);
 
 
 
B:=B*2;
 
 
 
w2(B);u2(B);
 
250

  3.3 两阶段封锁

  在一个另人吃惊的条件下,我们可以保证一致事务的合法调度是冲突可串行化的。这一条件称为两阶段封锁或2PL,在商用封锁系统中被广泛采用。2PL条件是:

  ·在每个事务中,所有封锁请求先于所有解锁请求。

  因此2PL中所指的“两阶段”是获得锁的第一阶段和释放锁的第二阶段。服从2PL条件的事务被称为两阶段封锁事务或2PL事务。

原创粉丝点击