数据库的并发控制

2008-07-14 10:05马宏强
电脑知识与技术 2008年18期

摘要:事务的并发执行可以显著提高系统的资源利用率,改善对事务的响应时间,但当多个事务对数据库进行并发操作时,如不加任何控制,可能会引起数据库的的数据不一致。本文讲述了并发操作引起的异常及如何解决异常问题。

关键词:并发操作;可串行化;两段锁协议;死锁

中图分类号:TP311文献标识码:A文章编号:1009-3044(2008)18-2pppp-0c

Database Concurrency Control

MA Hong-qiang

(Zhongwei in Ningxia and public security fire brigade,Zhongwei 750021,China)

Abstract:transactions interleaved concurrency can significantly improve the utilization rate of resources,improve the response time of the Panel, but a number of transactions for concurrent operation of the database, if without any control, may cause the database data inconsistencies. This paper described the concurrent operation caused by abnormal anomalies and how to resolve the problem.

Key words: interleaved concurrency;serializable;Two-Phase Locking;Deadlock

1 概述

数据库的重要特征是可以为多个用户提供数据共享。例如飞机定票数据库系统、银行数据库系统等都是多用户数据库系统。在这样的系统中,在同一时刻并行运行的事务数据目可达数百个。数据库管理系统允许共享的用户数目是数据库管理系统重要性能参数据之一。数据库管理系统(以下简称为DBMS)必须提供并发控制机制来协调并发用户的并发操作以保证并发事务的隔离性,从而达到数据库的一致性。

事务可以一个一个地串行执行,即每个时刻只有一个事务运行,其他事务必须等到这个事务结束以后方能运行。事务在执行过程中需要不同的资源,有时需要CPU,有时需要存取数据,有时需要I/O,有时需要通信。如果事务串行执行则许多系统资源将处于空闲状态。因此,为了充分利用系统资源,发挥数据库共享资源的特点,应该允许多个事务并行地执行。

2 并发操作引起的数据不一致性问题

事务是并发控制的基本单位,具有四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isotation)和持续性(Durability)。这四个特性也简称为ACID特性。保证事务ACID特性是DBMS的重要任务,而遭到破坏的原因之一是多个事务对数据库的并发操作造成的。

下面以飞机订票系统中的一个活动序列为例,说明并发操作带来的数据不一致问题。

①甲售票点(甲事务)读出的某航班的机票余额为A,设A=16;

②乙售票点(乙事务)读出的同一航班的机票余额为A,也为16;

③甲售票点卖出一张机票,修改余额为A=A-1,所以A为15,把A写回到数据库;

④乙售票点也卖出一张机票,修改余额为A=A-1,所以A为15,把A写回到数据库;

结果明明卖出两机票,数据库中机票余额只减少1。这种情况称为数据库的不一致性。这种不一致性是由并发操作引起的。在并发操作情况下,对甲、乙两个事务的操作序列的调度是随机的。若按上面的调度序列执行,甲事务的修改就被丢失。这是由于第④步中乙事务修改A并写回后覆盖了甲事务的修改。

2.1 不一致类型

仔细分析并发操作带来的数据不一致性包括三类:丢失修改、不可重复读和读“脏”数据。

2.1.1 丢失修改

以下为T1和T2两个事务的四个时刻的活动序列:

①T1读A=16。

②T2读A=16。

③T1将A=A-1写回,A=15。

④T2将A=A-1写回,A=15。

两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。

2.1.2 不可重复读

以下为T1和T2两个事务的三个时刻的活动序列:

①T1读A=50,B=100,计算A+B=150。

②T2读B=100,计算B=B*2=200,并写回B=200。

③T1读A=50,B=200,计算A+B=250。

不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。具体地讲,事可重复读包括三种情况:

(1)事务T1读取某一数据后(B=100),事务T2对其做了修改(B=200),当事务T1再次读该数据时,提到与前一次不同的值(B从100变成了200)。

(2)事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中的部分记录,当T1再次按相同条件读取数据时,发现某些记录神秘地消失了。

(3)事务T1按一定条件从数据库中读取某数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

后两种不重复读有时也称为幻影现象。

2.1.3 读“脏”数据

以下为T1和T2两个事务的三个时刻的活动序列:

①T1读C=100,计算C=C*2=200,写回C。

②T2读C=200。

③ROLLBACK,C恢复为100。

读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后(C=200),T1由于某种原因撤销,其修改作废,这时T1已修改过的数据就恢复原值(C=100),T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为“脏”数据,即为不正确的数据。

产生上述三类数据不一致性的主要原因是并发操作破坏了事务的隔离性,并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。并发控制的主要技术是封锁(Locking)。

3 封锁是实现并发控制的主要技术

封锁是实现并发控制的一个非常重要的技术。所谓封锁就是事务T在对某个数据对象(例如表、记录等)操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该数据对象有了一定的控制,在事务T释放客观存在的锁之前,其他的事务不能更新此数据对象。

基本的封锁类型有两种:排它锁(Exclusive Locks,简称X锁)和共享锁(Share Locks,简称为S锁)。排它锁又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务在T释放A上的锁之前是不能再读取和修改A。共享锁又称为读锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

在运用X锁和S锁这两种基本封锁,对数据对象加锁时,还需要约定一些规则,例如何时申请X锁或S锁、持锁时间、何时释放锁等,称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同就形成了各种封锁协议。

3.1 两段锁协议

计算机系统对并发事务中并发操作的调度是随机的,而不同的调度可能会产生不同的结果,那么哪个结果是正确的呢?如果一个事务运行过程中没有其他事务同时运行,也就是说它没有受到其他事务的干扰,那么就可以认为该事务的运行结果是正确的。因此将所有事务串行起来的调度策略一定是正确的调度策略。虽然以不同的顺序串行执行事务可能会产生不同的结果,但由于不会将数据库置于不一致的状态,因此都是正确的。

为了保证并发操作的正确性,DBMS的并发控制机制必须提供一定的手段来保证调度是可串行化的。从理论上讲,在某一事务执行时禁止其他事务执行的调度策略一定是可串行化的调度,这也是最简单的调度策略,但这种方法实际上是不可取的,这使提用户不能充分共享数据库资源。目前DBMS普遍采用两段封锁方法实现并发操作调度的可串行性,从而保证调度的正确性。

所谓两段锁协议是指所有事务必须分两个阶段对数据对象加锁和解锁。具体作法如下:

(1)在对任何数据对象进行读、写操作之前,首先要申请并获得对该数据对象的封锁;

(2)在释放一个封锁之后,事务不再申请和获得任何其他封锁。

所谓“两段”锁的含义是:事务分为两阶段,第一阶段是获得封锁,也称为扩展阶段。在这阶段,事务可以申请获得任何数据对象上的任何类型的锁,但是不能释放任何锁。第二阶段是释放封锁,也称为收缩阶段。在这阶段,事务可以释放任何数据对象上的任何类型的锁,但是不能再申请任何锁。可以证明,若并发执行的所有事务均遵守两段协议是可串行化调度的充分条件,而不是必要条件。也就是说若并发事务都遵守两段锁协议,则对这些事务的任何并发调度都是可串行化的;若对并发事务的一个调度是可串行化的,但不一定所有事务都符合两段锁协议。

以下为T1和T2两个事务的一种活动序列:

①T1事务SLOCK B,读B=2,赋值于Y(即Y=B=2),XLOCK A。

②T2事务SLOCK A,(由于A被加了X锁,所以加锁失败)等待A上锁的释放。

③T1事务计算A=Y+1,写回A=3。

④T1事务UNLOCK B,此时T2事务仍在等待A上锁的释放;T1事务UNLOCK A,此时T2事务仍在等待A上锁的释放。

⑤T2事务SLOCK A(由于A上锁已释放,所以加锁成功),读A=3,Y=A,XLOCK B,B=Y+1,写回B=4,UNLOCK B,UNLOC A。

在这个活动序列中,T1和T2都遵守两段锁协议,因此调度是可串行化的。

以下为T1和T2两个事务的另一种活动序列:

①T1事务SLOCK B,读B=2,赋值于Y(即Y=B=2),UNLOCK B,XLOCK A。

②T2事务SLOCK A(由于A被加了X锁,所以加锁失败),等待A上锁的释放。

③T1事务计算A=Y+1,写回A=3,UNLOCK A;此时T2事务仍在等待A上锁的释放。

④T2事务SLOCK A(由于A上锁已释放,所以加锁成功),读A=3,X=A,UNLOC A ,XLOCK B,B=X+1,写回B=4,UNLOCK B。

在这个活动序列中,T1和T2没有遵守两段锁协议,但调度是可串行化的。以上的两个例子可以说明若并发执行的所有事务均遵守两段锁协议是可串行化调度的充分条件,而不是必要条件。

4 死锁问题

4.1 死锁现象

由于两段锁协议并不要求事务必须一次将所有要使用的数据对象全部加锁,因此遵守两段协议的事务可能发生死锁。以下为T1和T2两个事务的一种活动序列:

①T1事务SLOCK B,读B=2。

②T2事务SLOCK A,读A=2。

③T1事务XLOCK A(由于A已被T2事务加上S锁,所以加X锁失败),等待A上锁的释放。

④T2事务XLOCK B(由于B已被T1事务加上S锁,所以加X锁失败),等待B上锁的释放。

在这个活动序列中,出现了T1在等待T2,而T2又在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁现象。

死锁现象在操作系统和一般并行处理中已有了成熟的解决方法。目前在数据库中解决死锁现象主要有两类方法,一类是采取一定措施来预防死锁的发生;另一类方法是允许发生死锁,采用一定手段定期诊断系统中有无死锁,若有则解除之。

4.2 死锁的预防

在数据库中,产生死锁的原因是两个或多个事务都已封锁了一些数据对象,然后又都请请求对已被其他事务封锁的数据对象进行加锁,从而出现相互死等待。防止死锁的发生其实就是要破坏产生死锁的条件。预防死锁通常有两种方法:

(1)一次封锁方法

一次封锁法要求每个事务必须一次将所有要使用的数据对象全部加锁,否则就不能继续执行。一次封锁虽然可以有效地防止死锁的发生,但也存在问题:第一,一次就将以后要用到的全部数据对象加锁,势必扩大了封锁的范围,从而降低了系统的并发度;第二,数据库中数据是来断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象。

(2)顺序封锁法

顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。顺序封锁法可以有效地防止死锁,但也同样存在问题:第一,数据库系统中封锁的数据对象极多,并且随着数据的插入、删除等操作不断地变化,要维护这样的资源的封锁顺序非常困难,成本太高;第二,事务的封锁请示可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些数据对象,因此也就很难按规定的顺序去施加封锁。

可见,在操作系统中广为采用的预防死锁的策略并不很适合数据库的特点,因此DBMS在解决死锁现象普遍采用的是诊断并解除死锁的方法。

4.3 死锁的诊断与解除

数据库系统中诊断死锁的方法与操作系统类似,一般使用超时法或事务等待图法。

(1)超时法

如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。超时法实现简单,但其不足也很明显。一是有可能误判死锁,事务因为其他原因使等待时间超过时限,系统会误以为发生了死锁。二是时限若设置得太长,死锁发生后不能及时发现。

(2)等待图法

事务等待图是一个有向图G(T,U)。T为结点的集合,每个结点表示正在运行的事务;U为边的集合,每条边表示事务等待的情况。若T1等待T2,则T1,T2之间边一条有向边,从T1指向T2。事务等待图动态地反映了所有事务的等待情况。并发控制系统周期性地(例如每隔1分钟)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁。

系统一但检测出存在死锁,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有的锁,使其他事务得以继续运行下去。当然对于撤消的事务所执行的数据修改操作必须加以恢复。

参考文献:

[1]Abraham Silberschatz(美),著.杨冬青,译.数据库系统概念,机械工业出版社,2002.2.

[2]庄成三.数据库系统原理及其应用,电子工业出版社,2000.6.

[3]俞盘祥.数据库系统原理.清华大学出版社,1988.

收稿日期:2008-03-18

作者简介:马宏强(1980-),男,宁夏固原人,助理工程师,学士,主要研究领域:计算机开发与应用。