2.4 两阶段锁
要实现冲突可串行化的调度,可以采用对数据库对象封锁的方式。当读取一个数据库对象时,可以对这个对象加共享锁(S锁);当修改一个数据库对象时,可以对这个对象加排他锁(X锁)。
加锁操作需要在访问数据库对象之前执行,而放锁的时机就比较微妙了。如果要实现一个真正的可串行化调度,那么需要借助两阶段锁机制。两阶段锁协议将事务的封锁过程分成了以下两个阶段。
• 增长阶段(Growing Phase):事务可以尝试申请任何类型的锁,但是这个阶段不允许释放锁。
• 收缩阶段(Shrinking Phase):事务可以放锁,但是禁止再申请新的锁。
我们可以借助反证法来证明两阶段锁的正确性,从两阶段锁的描述中可以看出,它的增长阶段和收缩阶段是严格区分的,也就是说加锁和放锁不会穿插进行。因此可以假设有一组事务{T1, T2, T3,…,Tn},该组事务采用两阶段锁的方法对本事务的操作进行控制。再假设它的优先图是有环的,也就是说该组事务的当前调度不是冲突可串行化的,如图2-10所示。
图2-10 通过反证法证明2PL的正确性
由优先图的关系可以得知,T1->T2有冲突依赖,即这两个事务在同一个对象上有冲突操作,那么必须是T1先释放锁,T2才被允许操作(读或写)这个对象(即对这个对象加锁)。如果T1和T2都遵守两阶段锁协议,那么,T1处于收缩阶段之后,T2处于增长阶段;进而可知,当T2处于收缩阶段时,T3则处于增长阶段。依此类推。
由于优先图有环,也就是存在Tn->T1的冲突依赖,所以当Tn处于收缩阶段时,T1才能处于增长阶段。而我们已经假设T1处于收缩阶段了,这违背了两阶段锁协议。由此可知,优先图中如果有环,就不满足两阶段锁协议的要求。
PostgreSQL中的封锁协议在2PL的基础上又有所增强,它基于S2PL(Strict-2PL)进行封锁。S2PL和2PL的主要区别在于放锁的时间,S2PL在事务提交时统一放锁。