并发控制
多用户数据库系统允许多用户同时使用。
事务串行执行,即每个时刻只有一个事务运行,其他事务必须等到这个事务结束以后方能运行。事务在执行过程中需要不同的资源,有时需要CPU,有时需要存取数据库,有时需要I/O,有时需要通信。如果事务串行执行,则许多系统资源将处于空闲状态。因此,为了充分利用系统资源发挥数据库共享资源的特点,应该允许多个事务并行地执行。
在单处理机系统中,事务的并行执行实际上是是这些并行事务的并行操作轮流交叉运行,这种并行执行方式称为交叉并发方式(Interleaved Concurrency),虽然单处理机系统听并行事务并没有真正地并行运行,但是减少了处理机的空闲时间,提高系统的效率。
多处理机系统中,每个处理机可以运行一个事务,多个处理机可以同时运行多个事务,实现多个事务真正的并行运行,这种种并行执行方式称为同时并发方式(Simultaneous Concurrency)。
事务并发可能会存取和存储不正确的数据,破坏事务的隔离性和数据库的一致性。所以DBMS必须提供并发控制机制,并发控制机制是衡量一个DBMS性能的重要标志之一。
并发控制概述
事务是并发控制的基本单位,保证事务ACID特性是事务处理的重要任务,而事务ACID特性可能遭到破坏的原因之一是多个事务对数据库的并发操作造成的。为了保证事务的隔离性和一致性,DBMS需要对并发操作进行正确调度。
关键概念:调度是指事务的执行次序。
并发操作带来的数据不一致性主要包括丢失修改、不可重复读和读“脏”数据。
1. 丢失修改:丢失修改是指事务T1与事务T2从数据库中读入同一数据并修改,事务T2的提交结果破坏了事务T1提交的结果,导致事务T1的修改被丢失。
2. 不可重复读:不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。不可重复读包括三种情况:
事务T1读取某一数据后:
(1)事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值。
(2)事务T2删除了其中部分记录,当事务T1再次读取数据时,发现某些记录神秘地消失了。
(3)事务T2插入了一些记录,当事务T1再次按相同条件读取数据时,发现多了一些记录。
后两种不可重复读有时也称为幻影(phantom row)现象。
3. 读“脏”数据:读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,事务T1由于某种原因被撤消,这时事务T1已修改过的数据恢复原值,事务T2读到的数据就与数据库中的数据不一致,是不正确的数据,又称为“脏”数据。
产生上述三类数据不一致性的主要原因是并发操作破坏了事务的隔离性。并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其它事务的干扰,从而避免造成数据的不一致性。
并发控制的主要技术有封锁(Locking)、时间戳(Timestamp)和乐观控制法,商用的DBMS一般都采用封锁方法。
封锁
封锁是指事务对数据对象操作之前向系统申请加锁。
1. 排它锁和共享锁
确切的控制由封锁的类型决定。基本的封锁类型有两种:排它锁(Exclusive lock,简记为X锁)和共享锁(Share lock,简记为S锁)。
排它锁又称为写锁。若事务T对数据对象A加上X锁,则允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。
共享锁又称为读锁。若事务T对数据对象A加上S锁,则其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
排它锁与共享锁的控制方式可以用下图的相容矩阵来表示。
封锁类型的相容矩阵
2. 封锁协议
在运用X锁和S锁对数据对象加锁时,需要约定一些规则,称为封锁协议(Locking Protocol)。封锁协议规定何时申请X锁或S锁、持锁时间和何时释放锁。
不同的封锁协议,在不同的程度上为并发操作的正确调度提供一定的保证。常用的封锁协议是三级封锁协议。
(1)一级封锁协议
一级封锁协议规定事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。
一级封锁协议可防止丢失修改,事务T1在读A进行修改之前先对A加X锁,当T2申请对A加X锁时被拒绝,T2只能等待T1释放A上的锁后T2获得对A的X锁,这时它读到的A已经是T1更新过的值15,在该值的基础上,T2将A的值进一步更新为14并送回到磁盘。这样就避免了丢失修改。
一级封锁协议能防止丢失修改
在一级封锁协议中,如果是读数据,不需要加锁的,所以它不能保证可重复读和不读“脏”数据。
一级封锁协议不能防止读“脏”数据
一级封锁协议不能不能保证可重复读
(2)二级封锁协议
二级封锁协议在一级封锁协议基础上,增加规定事务T在读取数据R前必须先加S锁,读完后即可释放S锁。
二级封锁协议可以防止丢失修改和读“脏”数据。事务T1在对C进行修改之前,先对C加X锁,修改其值后写回磁盘。这时T2请求对C加S锁,因T1已在C上加了X锁,所双T2只能等待。T1因某种原因被撤销,C恢复原值100,T1释放加在C上的X锁后T2获得C上的S锁,读C=100,与数据库中的C值一致。这就避免了T2读“脏”数据。
二级封锁协议能防止读“脏”数据
在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。
(3)三级封锁协议
三级封锁协议在一级封锁协议基础上,增加规定事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。
三级封锁协议可防止丢失修改、读脏数据和不可重复读。事务T1在读A、B之前,先对A、B加S锁,这样其它事务只能再对A、B加S锁,而不能加X锁,即其他事务只能读A、B,而不能修改它们。所以当T2为修改B而申请对B的X锁时被拒绝而只能等待T1释放B上的锁。T1为验算再读A、B,这时读出的B仍是100,求和结果仍为150,即可重复读。T1结束才释放A、B上的S锁。T2才获得对B的X锁。
图6.16 三级封锁协议能保证可重复读
三级封锁协议可以解决并发事务在执行过程中遇到的3种数据不一致性问题,但是却带来了其它的问题,如活锁和死锁。
6.3.3活锁和死锁
1. 活锁
如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求……T2有可能永远等待,这就是活锁的情形,如图6.17所示。
关键概念:活锁是指在事务并发执行过程中,某个事务总有机会获得锁却永远也没得到锁。
活锁
避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对这些事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。
2. 死锁
如果事务T1封锁了数据R1,T2封锁了数据R2,然后T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁。接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁。这样就出现了T1在等待T2,而T2又在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁。
关键概念:死锁是指在事务并发执行过程中,多个事务处于相互等待的状态。
死锁
死锁的问题在操作系统和一般并行处理中已做了深入研究,目前在数据库中解决死锁问题主要有两类方法,一类方法是采取一定措施来预防死锁的发生,另一类方法是允许发生死锁,采用一定手段定期诊断系统中有无死锁,若有则解除之。
3. 死锁的预防
产生死锁的原因是两个或多个事务都已封锁了一些数据对象,然后又都请求对已为其他事务封锁的数据对象加锁,从而出现死等待。预防死锁的发生就是要破坏产生死锁的条件。预防死锁通常有两种方法:
(1)一次封锁法
要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。
存在问题:
第一,一次就将以后要用到的全部数据加锁,势必扩大了封锁的范围,从而降低了系统的并发度。
第二,数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象,为此只能扩大封锁范围,将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度。
(2)顺序封锁法
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
存在的问题:
第一,数据库系统中可封锁的数据对象极其众多,并且随着数据的插入、删除等操作而不断地变化,要维护这样极多而且变化的资源的封锁顺序非常困难,成本很高。
第二,事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁。
4. 死锁的诊断与解除
(1)超时法
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。
缺点:一是有可能误判死锁,事务因为其他原因使等待时间超过时限,系统会误认为发生了死锁。二是时限设置得太长,死锁发生后不能及时发现。
(2)等待图法
事务等待图是一个有向图G=(T,U)。T为结点的集合,每个结点表示正运行的事务;U为边的集合,每条边表示事务等待的情况,若T1等待T2,则T1,T2之间划一条有向边,从T1指向T2。
事务等待图动态地反映了所有事务的等待情况。并发控制子系统周期性地(比如每隔数秒)生成事务等待图,并进行检测。如果发现图中存在回路,则表示系统中出现了死锁。
通常采用的方法是选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有的锁,使其它事务能继续运行下去。当然,对撤销的事务所执行的数据修改操作必须加以恢复。
6.3.4 并发调度的可串行化
DBMS对并发事务不同的调度可能会产生不同的结果,那么什么样的调度是正确的呢?
定义 多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行它们时的结果相同。这种并行调度策略称为可串行性(Serializable)的调度。
可串行性是并行事务正确性的唯一准则。
6.3.5 两段锁机制
DBMS普遍采用两段锁(Two-Phase Locking,简称2PL)协议的方法实现并发调度的可串行性,从而保证调度的正确性。
所谓两段锁机制是指所有事务必须分两个阶段对数据项加锁和解锁。
在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁;
在释放一个封锁之后,事务不再获得任何其他封锁。
所谓“两段”锁的含义是,事务分为两个阶段,第一阶段是获得封锁,也称为扩展阶段。在这阶段,事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁。第二阶段是释放封锁,也称为收缩阶段。在这阶段,事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁。
例如,事务1的封锁序列:
Slock A ... Slock B ... Xlock C ... Unlock B ... Unlock A ... Unlock C;
事务2的封锁序列:
Slock A ... Unlock A ... Slock B ... Xlock C ... Unlock C ... Unlock B;
事务1遵守两段锁机制,而事务2不遵守两段机制。
并行执行的所有事务均遵守两段锁机制,则对这些事务的所有并行调度策略都是可串行化的。
所有遵守两段锁协议的事务,其并行执行的结果一定是正确的。但是,需要特别指出的是,事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件,可串行化的调度中,不一定所有事务都必须符合两段锁协议。如图6.22(b)中,T1和T2事务均不遵守两段锁协议,但该并发调度结果与串行调度中一种结果是相同的,所以是可串行化调度。图6.22(c)中,T1和T2事务均不遵守两段锁协议,该并发调度结果与任何一种串行调度中结果都不同,所以是不可串行化调度。
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议。但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。如图6.23所示,,T1和T2事务均遵守两段锁协议,但发生了死锁。
6.3.6封锁的粒度
封锁对象的大小称为封锁粒度(Granularity)。封锁对象可以是逻辑单元,也可以是物理单元。以关系数据库为例,封锁对象可以是这样一些逻辑单元:属性值、属性值集合、元组、关系、索引项、整个索引直至整个数据库;也可以是这样一些物理单元:页(数据页或索引页)、物理记录等。
封锁粒度与系统的并发度和并发控制的开销密切相关。直观地看,封锁的粒度越大,数据库所能够封锁的数据单元就越少,并发度就越小,系统开销也越小;反之,封锁的粒度越小,并发度较高,但系统开销也就越大。
如果在一个系统中同时支持多种封锁粒度供不同的事务选择是比较理想的,这种封锁方法称为多粒度封锁(Multiple Granularity Locking)。选择封锁粒度时应该同时考虑封锁开销和并发两个因素,适当选择封锁粒度以求得最优的效果。一般说来,需要处理大量元组的事务可以以关系为封锁粒度;需要处理多个关系的大量元组的事务可以以数据库为封锁粒度;而对于一个处理少量元组的用户事务,以元组为封锁粒度就比较合适了。
1. 多粒度封锁
定义 多粒度树:以树形结构来表示多级封锁粒度。根结点是整个数据库,表示最大的数据粒度,叶结点表示最小的数据粒度。图6.24级出了一个三级粒度树。根结点为数据库,数据库的子结点为关系,关系的子结点为元组。也可以定义四级粒度树,例如数据库、数据分区、数据文件、数据记录。
定义 多粒度封锁协议:允许多粒度树中的每个结点被独立地加锁,对一个结点加锁意味着这个结点的所有后裔结点也被加以同样类型的锁。
因此,在多粒度封锁中,一个数据对象可能以两种方式封锁,显式封锁和隐式封锁。
显式封锁是直接加到数据对象上的封锁;隐式封锁是由于其上级结点加锁而使该数据对象加上了锁。
2. 意向锁
意向锁的含义是对任一结点加基本锁,必须先对它的上层结点加意向锁,如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁。
三种常用意向锁:意向共享锁(Intent Share Lock,简称IS锁);意向排它锁(Intent Exclusive Lock,简称IX锁);共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁)。
(1)IS锁
如果对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁。
(2)IS锁
如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。
(3)SIX锁
如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX = S + IX。
图6.25给出了这些锁的相容矩阵,从中可以发现这五种锁的强度如图6.26所示的偏序关系。锁的强度是指它对其它锁的排斥程度。一个事务在申请封锁时以强锁代替弱锁是安全的,反之则不然。
Y=Yes,表示相容的请求 N=No,表示不相容的请求
具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销,已经在实际的数据库管理系统产品中得到了广泛应用。
网友评论