判断一个并发控制算法是否可串行化,可以先根据三种情况判断:
1、构造Lost Update(T2读X,T1写X提交,T2再写X、T2提交),看这种算法是否允许。
2、构造Dirty Read(T1读到了T2的未提交的写X,T1提交,T2提交),看这种算法是否允许。
3、构造Unrepeatable Read(T1读X,T2写X提交,T1再读,读到不同的X,T1提交),看这种算法是否允许。
4、构造write skew(T1读X,T2读Y,T1写Y,T2写X,T1提交,T2提交),看这种算法是否允许。
如果有任一种异常情况算法不能禁止,说明这个算法不是可串行化的。
这些异常的共同特点就是:从读到的值和最终的结果来看,无论T1在T2前执行还是在T2后执行,都不可能,是一种矛盾的情况。
还会有其它异常(write skew),这些异常都会推导出T1、T2执行顺序的矛盾情况,也就是说,所有的异常,都会构造出优先图的环。
可以根据经验的几种异常,构造对象的读写调度,如果这种算法不能禁止这种调度,说明此算法实现串行化有问题。一般化讲,只要能构造出一种调度,它的优先图有环,而且被这种算法允许,那么这种算法实现串行化就有问题。
write skew 是MVCC才会有的并发异常,对于只是用2PL的并发控制(如SQL Server),是不会存在写偏序的。
使用严格的两阶段锁,是不会出现异常的,但是并发性不理想,为了提高并发性,人们想出了各种的并发控制算法和变体(如MVCC,Validation),但是这些方法提高了并发性的同时,也引入了新的(2PL所不会发生的)异常,(如MVCC引入了write skew,在2PL中是一开始就被禁止的),所以又研究出新技术解决这些变体产生的异常,使之能够严格序列化,如SSI。
另一种思路是,数据库为了并发性,不保证绝对不产生异常(降低隔离级别),而是由应用程序端,在低隔离级别时,避免执行某些SQL操作(避免踩雷,而不是没有雷),从而保证事务之间不会出现异常(非串行化)。
不同隔离级别的事务混合执行,必然不能保证数据库系统的一致性。既然并发控制算法也称为协议,就是多个事务之间的事情,意味着多个事务必须同时遵守同样的协议(隔离级别)才能达到串行化的效果。
(对于互不干涉的两个模块,可以一个模块的所有事务在serializable级别,另一个模块的所有事务在read commited级别)
所谓事务的隔离级别只对自己有效,这应该是从ANSI SQL Isolation Level角度理解的,即:
1)本事务隔离级别设为uncommited read时,不允许其它事务写本事务已经写的数据,可以读到其它事务的未提交的数据。
2)本事务隔离级别设为read commited 时,不允许其它事务写本事务已经写的数据,能读到本事务开始后其它事务已提交的数据。
3)本事务隔离级别设为repeatable read 时,不允许其它事务写本事务已经写的数据,本事务第一次读数据和第二次读数据之间,其它事务修改了同样的数据并已提交,仍然保证本事务两次读到的是一样的。
4)本事务隔离级别设为serializable 时,在3)的基础上,本事务杜绝了幻读。
设置了不同的隔离级别后,单独只看本事务,可保证上面的特性,但是这样就能保证整个数据库系统内的串行化了吗?例如,对于PostgreSQL,T1为serializable ,T2为read commited可以保证不出现write skew吗?并不能,况且PG的repeatable read可以保证不出现幻象,但仍会有serialization anomaly。
接下来就是用这种思维,评估MVCC和PG的并发控制算法!