第十一章 并发控制
在多处理机系统中,每个处理机可以运行一个事务,多个处理机可以同时运行多个事务,实现多个事务并行运行,这就是同时并发方式。当多个用户并发存取数据库时会产生多个事务同时存取同一事务的情况,如果对并发操作不加控制就可能破坏事务的一致性和数据库的一致性
11.1 并发控制概述
事务是并发控制的基本单位,保证食物的ACID特性是事务处理的重要任务。为了保证事务的隔离性和一致性,DBMS需要对并发操作进行正确的调度。
并发操作带来的数据不一致性包括以下几种情况:
1.丢失修改
两个事务T1和T2同时读入同一数据并修改,T2的提交结果破坏了T1的提交结果,导致T1修改被丢失。
2.不可重复读
不可重复读是指事务T1读取数据后,事务T2执行更新操作,使得T1无法再现前一次的读取结果。包含三种情况:
- 事务T1读取数据后,T2对其进行了修改,T1再次读数据的时候,得到了与前一次不同的值
- 事务T1按一定条件从数据库读取了数据记录后,T2删除了部分记录,当T1再次读数据的时候,发现某些数据消失了。
- 事务T1按一定条件从数据库读取某些数据之后,T2插入了一些记录,当T1再次按相同条件读取数据的时候,发现多了一些记录
后两种不可重复读被称为数据幻影现象
3.读脏数据
指事务T1修改某一数据并且写回磁盘,T2读取到该数据后,T1事务被撤销了,则T2中读到的数据是被撤销前的数据,为脏数据。
上述三类数据不一致的主要原因是并发操作破坏了事务的隔离性,并发机制就是要用正确的方式调度并发操作,使得一个用户事务的执行不受其他事务干扰,来避免数据的不一致性
主要控制技术有封锁、时间戳、乐观控制法和多版本并发控制,下面依次介绍这些方法。
11.2 封锁
封锁是实现并发控制的一个非常重要的技术。封锁指的是事务T在对某个数据对象进行操作之前,先请求系统对该对象进行加锁,仅当他完成操作后才允许其他事务对该对象进行操作。
基本的封锁类型有两种:排他锁(exclusive lock,简称X锁)和共享锁(share lock,简称S锁)。
排他锁又称为写锁,一般写操作使用,事务T给数据对象A加X锁后,不允许任何事物给A加锁和访问A,直到T释放A上的锁.
共享锁又称为读锁,如果事物T对对象A加上S锁。则该事务可以读但不可以修改A,而且其他事务只能对A加S锁而不能加X锁,直到所有的S锁释放。
11.3 封锁协议
在运用封锁的时候,还需要约定一些规矩,这些协议被称为封锁协议。对封锁方式制定不同的规则,就形成了各种不同的封锁协议。
1.一级封锁协议
一级封锁协议是指,事务T在修改数据R之前必须对其先加X锁,直到事务结束的时候才释放。事务结束包括COMMIT和ROLLBACK。一级封锁协议可防止丢失修改,并且保证事务T是可以恢复的。在一级封锁协议中, 仅仅是读数据而不修改,是不需要加锁的,因此不能保证可重复读,也不能保证不读脏数据。
2.二级封锁协议
二级封锁协议是指,在以及封锁协议基础上增下了事务T在读取数据R前必须先对其加上S锁,读完后方可释放。
二级封锁协议可以防止丢失修改,并且进一步防止读取脏数据。
3.三级封锁协议
三级封锁协议是指,在以及封锁协议上增加了事务T在读取数据R之前必须先对其加上S锁,直到事务结束才释放
该跟锁协议出了防止丢失修改和读取脏数据外,还进一步防止了不可重复读。
上述封锁协议主要区别在于什么操作需要申请封锁,以及何时释放锁。
11.4 活锁和死锁
活锁
如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2开始等待;如果此时T3也请求封锁R,那么当T1释放了R上的封锁后会首先通过T3的封锁请求,如果后面还有源源不断的事务请求的话,T2会陷入持续忙等的情况,但是忙等的情况还是有机会解除的,这就说活锁
避免活锁的简单方法是采用先来先服务策略。
死锁
如果T1封锁R1,T2封锁了R2,此时T1申请封锁R2,而T2又申请封锁R1。此时就会导致死锁——两个事务互相需要彼此的资源,但是两者都不释放锁。这就类似于两个人互相持枪对峙,然后两边都要求对方先放下枪。
目前数据库中解决死锁主要有两类方法,一类是预防死锁的发生,一类是采用一定手段解除死锁。
1.死锁的预防
- 一次死锁法:要求每一个事务必须一次将所有的数据全部加锁,否则不能执行。这能够有效防止死锁的发生,但是由于扩大了封锁范围,降低了系统的并发度。第二,在动态运行时,一些数据原先不需要封锁的也可能变为要封锁的对象,事先难以精确确定每个事务要封锁的数据对象。也就是该法对于编译锁支持较好,但是对于动态运行时锁就有点无能为力了。
- 顺序封锁法:顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按照该顺序实施封锁。比如B树索引中,可以从根节点开始,逐步封锁下一层的节点。该法可以很有效地防止死锁,但是数据库中封锁的对象极多,要维护这样的资源的封锁顺序成本极高。而且事务的封锁请求会随着事物的执行而动态变化。
可见,在操作系统中普遍采用的死锁预防并不太适合数据库,因此DBMS在解决死锁问题上主要采用诊断并解除死锁的方法。
2.死锁的诊断与解除
数据库系统中诊断死锁的方法与操作系统类似,一般使用超时法或者事物等待图法。
- 超时法
如果一个事务等待时间显然超出了规定时限,则认为发生了死锁。该法很简单,但是可能会误判死锁,而且对超时时限的设置要求比较严格
一旦检测到系统中存在死锁,使用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务所有的锁,使得其他事务可以继续运行。
11.5 并发调度的可串行性
可串行化调度
多个事务正确地并发执行后的结果应该与按某一次序串行地执行这些事务时的结果相同
可串行性是并发事务正确调度的准则。
冲突操作指的是不同的事务对同一个数据的读写操作和写写操作。一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务的不冲突次序得到另外一个调度Sc‘,如果Sc’是串行的,则称Sc为冲突可串行化。如果一个调度是冲突可串行化的,则一定是可串行化调度。
冲突可串行化调度是可串行化调度的充分不必要条件
11.6 两段锁协议
为了保证并发调度的正确性,数据库管理系统的并发控制机制提供一定缩短来保证调度是可串行化的。目前普遍使用的是两段锁协议。
两段锁协议指所有事务必须分两个阶段对数据项进行加锁和解锁:
- 对任何数据进行读写操作之前,首先申请并获得对该数据的封锁
- 在释放一个封锁之后,事务不再申请和获得任何其他的封锁。
两段锁的含义是,事务分为两个阶段,第一阶段是获得封锁,这个阶段事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁。第二阶段是释放封锁,这个阶段,事务可以释放任何数据项上的任何封锁,但是不能申请任何锁。
事务遵循两段锁协议是可串行化调度的充分条件,而不是必要条件。
11.7 封锁的粒度
封锁对象的大小称为封锁粒度。封锁的对象可以是逻辑单元,比如属性值的集合、元组、关系、索引等,也可以是物理项,比如页、物理记录等。
封锁粒度与系统的并发度和并发控制的开销密切相关。封锁粒度越大,数据库能封锁的数据单元越少,并发度越低;反之则并发度较高,但是由于需要调度系统资源,频繁加锁解锁,系统开销大。
如果一个系统中同时支持多种封锁粒度提供不同事务选择是比较理想的,这种封锁方法称为多粒度封锁
多粒度封锁
讨论多粒度封锁首先需要定义多粒度树。多粒度树的根节点是整个数据库,表示最大的数据粒度,叶节点表示最小的封锁粒度。比如如下一棵三级粒度树:
多粒度封锁协议允许多粒度树中每个节点被独立地加锁。对一个节点加锁代表着其后裔节点也被加以同样的锁。
因此,显示封锁指的是事务直接为数据对象加锁;隐式封锁是该数据对象没有被独立加锁,是由于其上级节点加锁而使得该数据对象加上了锁。
一般对某个数据对象加锁,系统除了要检查数据对象时有无显式封锁,还需要检查其上级节点有无被封锁,以及其所有子树节点。这样检查的效率很低,因此人们引进了一种新型锁——意向锁
意向锁
意向锁的含义是如果对一个节点加了意向锁,说明该节点的下层节点正在被封锁;对任意节点加锁时,必须先对他的上层节点加意向锁。
比如对任意元组加锁的时候,必须先对它所在的数据库和关系加意向锁。下面介绍三种常见意向锁:
11.8 其他并发控制机制
并发控制的方法出了封锁技术外还有时间戳方法、乐观控制法和多版本并发控制等。时间戳方法是给每一个事务盖上一个时标,并按照这个时间戳来解决事务的冲突操作
多版本并发控制
版本是指数据库中数据对象的一个快照,记录了数据对戏那个某个时刻的状态。
多版本并发控制和封锁机制相比,主要的好处是消除了数据库中数据对象的读和写操作的冲突,提高了系统性能。