一、引言
目前主流的关系数据库通常都允许多个用户同时使用和共享,所以也都具有并发控制的机制,也就是控制数据库,防止多用户并发使用数据库时造成数据错误和程序运行错误,以保证数据的完整性。
二、事务与并发控制的概念
当多用户并发存取数据时,就会产生多个事务同时存取同一数据的情况,从而引起严重的数据错误和程序运行错误。那么我们来看,什么是事务及并发控制呢?
事务就是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的很小的工作单位。例如,在SQL语言中,定义事务的语句有三条:
BEGINTRANSACTION;
COMMIT;
ROLLBACK;
其中的BEGINTRANSACTION 是事务开始的标记,而以COMMIT或者ROLLBACK结束,COMMIT 用于提交事务的所有操作,ROLLBACK 则在事务运行过程中一旦发生了某种故障而使事务无法继续执行的时候,系统就将事务中对数据库的所有刚刚完成的操作全部撤消,滚动回到事务开始时的状态。为了充分利用系统资源,使数据库的共享资源得以有效利用,必须可以使多个事务并行的执行,而数据库对并行执行的事务进行的控制就是并发控制。
三、事务进行并发操作可能引起的数据不一致问题
由于种种原因,都可能引起数据库的数据遭到破坏,比如多个事务在并行运行的时候,不同的事务的操作产生了交叉执行,或者,事务在运行过程中被强行停或者中断。因此,事务在进行并发操作的时候很可能引起数据的不一致,下面我们看一个具体的例子。例如飞机票的联网销售系统,如果有以下的操作序列:
1.甲售票处(设置为T1 事务)读出某班次的机票剩余数A ,
设A=20
2.乙售票处(设置为T2 事务)读出同班次的机票剩余数A ,也是20
3.甲售票处(T1 事务)卖出一张机票,修改剩余数减一(A← A-1),把A=19 写回数据库中
4.乙售票处(T2 事务)也卖出一张机票,修改剩余数减一(A← A-1),把A=19 写回数据库中从这些操作中,我们看到,乙售票处的修改数据覆盖了甲售票处修改的数据,实际发生了两张机票的销售,而数据库中却错误的存入19,少了一张。参看图1 的情况。这种情况是并发操作引起数据不一致的第一种情况,叫做丢失修改(Lost Update),第二种是不可重复读(Non-Repeatable Read),
第三种是读“脏”数据(DirtyRead)。
下边看第二种情况,不可重复读是指事务T1 读数据以后,T2执行更新操作,就使T1无法再现原先读取的数据,得到与上一次不同的结果,例如图2 。
读“脏”数据是指T1 修改某数据并将其写回数据库,T2 读取同一数据后,T1 由于某种原因被撤消,T1 执行回滚,恢复到原始的数据,T2 就读取到了过程中的一个作废的数据,这个数据就是一种垃圾数据,称之为“脏”数据,也是不正确的。参看图3 。
从以上例子我们看到,数据不一致性的主要原因就是并发操作没有对事务进行一定的隔离,所以,正确的调度应该使一个用户的事务不受到其他事务的干扰,从而避免数据的不一致性。
四、在并发控制中采用封锁协议解决数据的不一致性并发控制的主要方法是封锁(Locking)。就是要用正确的方式调度并发操作,使一个用户的事务在执行过程中不受其他事务的干扰,从而避免造成数据的不一致性。封锁是使事务对它要操作的数据有一定的控制能力。封锁通常具有3 个环节:第一个环节是申请加锁,即事务在操作前要对它将使用的数据提出加锁申请; 第二个环节是获得锁,即当条件成熟时,系统允许事务对数据进行加锁,从而事务获得数据的控制权;第三个环节是释放锁,即完成操作后事务放弃数据的控制权。
封锁是实现并发控制的一个非常重要的技术。所谓封锁就是事务T在对某个数据对象例如表、记录等操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该 数据对象有了一定的控制,在事务T释放它的锁之前,其它的事务不能更新此数据对象。 基本的封锁类型有两种:排它锁(Exclusive locks 简记为X锁)和共享锁(Share locks 简记为S锁)。
排它锁又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其它事务在T释放A上的锁之前不能再读取和修改A。
共享锁又称为读锁。若事务T对数据对象A加上S锁,则其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其它事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
在 运用X锁和S锁这两种基本封锁,对数据对象加锁时,还需要约定一些规则,例如应何时申请X锁或S锁、持锁时间、何时释放等。我们称这些规则为封锁协议 (Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。下面介绍三级封锁协议。三级封锁协议分别在不同程度上解决了丢失的修改、不 可重复读和读"脏"数据等不一致性问题,为并发操作的正确调度提供一定的保证。下面只给出三级封锁协议的定义,不再做过多探讨。
执行了封锁协议之后,就可以克服数据库操作中的数据不一致所引起的问题。
从图4 的情况我们看到事务T1 在执行过程中独自占用并加X锁,直到处理完之后再释放锁,T2 虽然也需要使用,但是在封锁协议的约束之下,T2 所要求的X 锁就被拒绝,因此必须处于等待状态,直到T1 释放之后,T2 才获得使用的权利,这样就不会发生使用冲突,避免了数据的丢失。这里我们看到,此处实际上是执行了一级封锁协议。
通过图5 ,能够清楚的看到,由于施行了封锁协议,使事务T1 使用了共享锁占用A,B 两块数据,这样T2 需要加上的X 锁就无法实现,(如果是S 锁,虽然可以加上,但也不能够随便修改数据,只是读取一下数据。)当T1 释放锁之后,T2 就可以得到并使用锁了,这样读取的数据B 仍然还是100,不影响A+B 的结果,这就是可重复读取。因此我们看到,其实这里用的就是三级封锁协议。
参看图6,事务T1 在对数据C 修改之前,先加上了X锁,修改后写回数据库,这时T2 请求在C上添加S 锁,因为T1 加了X 锁,T2只好等待,当T1 因为某种原因撤销了修改的数据后,C 就恢复了原来的数据100,等T1 释放X 锁后T2 获得C 上的S锁, 读到的还是C = 1 0 0,因此避免了读出“脏”数据。这里使用的其实就是二级封锁协议。
通过以上内容,数据库由于采用一定的封锁协议避免了数据的不一致性问题,这使得数据库的并发控制有效而且有益,从而使得多项事务可以并行的操作数据库的共享资源了。这就是数据库合理的进行调度,避免了冲突,避免了数据的不一致。