2.1 并发的异常现象

如果事务调度系统能够保证事务逐个执行而不交叉,那么就说事务符合可串行化的要求。所谓的事务可串行化,指事务虽然是并发交叉执行的,但执行结果和串行执行的结果一致。串行执行的效率比较低,为了提高系统的吞吐量,事务需要并发执行,因此在数据库中就需要实现并发控制机制,这样既能满足事务隔离性的要求,又能提高数据库的并发性能。

事务的并发控制机制主要是防止出现由于事务的并发执行导致的异常现象。因此,只有了解了这些异常现象,才能更好地理解事务并发控制实现的机制。

在ANSI SQL的标准文档中,规定了事务并发执行可能导致的3种异常现象,分别是脏读、不可重复读和幻读。下面逐一看一下这些异常现象的定义。

P1,脏读:事务T1修改了一个元组,在事务T1提交之前,事务T2就读到了事务T1修改的这个元组。由于事务T1还没有提交,所以事务T2读到的元组是“脏”元组,如图2-1所示。

图2-1 脏读示意图

P2,不可重复读:事务T1读取了一个元组,然后事务T2修改或删除了这个元组,并且事务T2提交了。这时候事务T1如果再次读取这个元组,就会发现元组已经被修改了或者被删除了。也就是说,事务T1重复读取一个元组却获得了不同的值,如图2-2所示。

图2-2 不可重复读示意图

P3,幻读:事务T1按照某个谓词读取到一组元组的集合,然后事务T2插入一个新的元组。这个新元组同样满足事务T1中的谓词条件,元组被插入后,事务T2提交。此时如果事务T1再次按照同样的谓词读取元组,就会读取到不同的元组集合(新插入的元组被读到),事务T1的回滚有可能覆盖事务T2的修改,如图2-3所示。

图2-3 幻读示意图

把异常现象定义成脏读、不可重复读、幻读是不充分的,一些专家对此也提出了异议,在论文A Critique of ANSI SQL Isolation Levels中对异常现象进行了更详细的划分,除了已经定义的3种异常,还定义了一些新的异常现象。

P0,脏写:事务T1修改了某个元组,在T1提交或回滚前,事务T2又修改这个元组,这就导致了脏写。假设事务T1最终回滚了,而事务T2的修改是基于事务T1的修改而做的,此时T2的执行结果是错误的,如图2-4所示。

图2-4 脏写示意图

P4,丢失更新:事务T1先是读取了元组中的某个值,然后事务T2修改了这个值,此时事务T1并不知道事务T2修改了这个值,它还是根据自己以前读到的值去做更新(例如做+1操作),这将会覆盖事务T2更新的值,产生了丢失更新异常,如图2-5所示。

图2-5 丢失更新示意图

丢失更新和脏写的最主要区别在于,丢失更新是基于先前读到的值进行更新的,而脏写则强调的是不同写操作之间对值的覆盖导致异常。

A5包含的是违反约束的两种异常现象,主要包括了读偏序和写偏序。

A5A,读偏序:假设xy具有x+y>0的一致性约束,事务T1读取了元组x的值。这时事务T2同时更新了xy的值,然后事务T2提交,事务T1再去读y的值,那么T1读到的xy的值就可能违反了一致性约束,因为它读到的x是事务T2提交之前的值,而y则是事务T2提交之后的值。如图2-6所示,此时x+y=50+(-60)=-10<0,违反了一致性约束。

图2-6 读偏序示意图

A5B,写偏序:假设xy具有x+y>0的一致性约束,事务T1读取了x=50和y=50,然后事务T2读取x=50和y=50。事务T2更新x的值为-40,此时它认为x+y =10 > 0,满足一致性约束。事务T1更新了y的值为-40,此时它也认为x+y = 10 > 0 满足一致性约束。但两个事务提交之后,x+y = -80 < 0,违反了一致性约束(注:严格的两阶段提交,即S2PL,不会出现这种异常,因为在事务T1和T2读取xy的值时会加读锁,并且读锁会持续到事务提交,由于读写冲突,S2PL能保证两个事务无法同时更新xy的值,会出现锁等待或者死锁,在SSI相关的章节中会继续介绍写偏序异常,因为在MVCC机制下,读写互相不阻塞,需要借助SSI方法解决该异常),如图2-7所示。

图2-7 写偏序示意图