背景
并行事务(一个事务可包含多个基本操作)的执行调度是随机的,不同的调度可能产生不同的结果,有些调度的结果是我们期望的,也就是正确的结果,而有些调度的结果不是我们期望的,也做是不正确的结果。
例如,假设事务A和B分别包含两个基本操作(A1, A2和B1, B2),他们可能产生如下的调度时序:
Time 1 | Time 2 | Time 3 | Time 4 | |
事务A | A1 | A2 | ||
事务B | B1 | B2 |
也可能产生如下调度时序:
Time 1 | Time 2 | Time 3 | Time 4 | |
事务A | A1 | A2 | ||
事务B | B1 | B2 |
或者如下调度时序:
Time 1 | Time 2 | Time 3 | Time 4 | |
事务A | A1 | A2 | ||
事务B | B1 | B2 |
等等。
串行调度:
如果把所有并行事务的执行都串连在一起,中间没有交叉执行的事务,这样的调度称为串行调度,串行调度的执行结果一定是正确的。
但是串行调度显然发挥不了并行系统的特点,所以需要研究具有串行调度效果的并行调度方法。
可串行性
当且仅当一组并发事务的交叉调度产生的结果和这些事务的某一个串行调度的结果相同,则称这个交叉调度是可串行化。
可串行化是并行事务正确性的准则,一个交叉调度,当且仅当它是可串行化的,它才是正确的。
两段锁协议是保证并行事务可串行化的方法。
两段锁协议(2PL:Two-Phase Locking)
两段锁协议规定所有的事务应遵守的准则:
在对任何数据进行读、写操作之前,首先要获得该数据的锁。
在释放一个锁之后,事务不再获取其它任何封锁。
因此我们把事务的执行分为两个阶段:
第一阶段是获得锁的阶段,称为加锁节点,或者扩展阶段。
对任何数据进行读操作之前要获得S锁,在进行写操作之前要获得X锁;如加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
第二阶段是释放锁的阶段,称为解锁阶段,或者收缩阶段。
当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
结论:
对于遵循两段锁协议的事务,他们的任何调度都是可串行化的。
注意这是一个充分条件,不是必要条件,即遵守两段锁协议的事务都是可串行化的,但是可串行化的事务不一定都需要遵守两段锁协议。
两段锁协议的死锁
另外必须指出,遵循两段锁协议的事务有可能发生死锁。例如事务T1 、T2同时处于加锁阶段,两个事务都坚持请求加锁对方已经占有的数据锁,导致死锁。为此,引入了一次加锁法的办法。
一次加锁法
一次加锁法要求事务必须在加锁阶段一次性把所有要使用的数据全部加锁,否则就不能继续执行。而在解锁阶段,则不需要一次性解锁,可以在一个数据不再使用的时候就释放对应的锁,使得其他等待加锁的事务可以马上获取,增加并行度。
总结,尽管一次加锁法遵守两段锁协议,但两段锁协议并不要求事务必须一次性将所有要使用的数据全部加锁,这就是遵守两段锁协议仍可能发生死锁,而使用一次加锁法则不会发生死锁的原因所在。