第十一章 并发控制
在单处理机系统中,事务的并行执行实际上是这些并行事务的并行操作轮流交叉运行。
11.1 并发控制概述
事务是并发控制的基本单位。
并发控制的目的:保证事务的隔离性和一致性
任务:
- 对并发操作进行正确调度
- 保证事务的隔离性
- 保证数据库一致性
并发操作带来的数据不一致性:
1、丢失修改:读入同一数据并修改
2、不可重复读:读第一次和读第二次的结果不一样,因为别人在中间修改插入删除了某些数据
3、读“脏”数据:T1修改某一数据并写回,T2读取,T1撤销操作,T2的数据便是脏数据
原因:并发操作破坏了事务的隔离性
并发控制机制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性
并发控制的主要技术有
封锁(银行、证券)、时间戳、乐观控制法和多版本并发控制
封锁是一种悲观的技术,认为冲突总在发生,因此控制并发事务。
乐观控制法认为冲突不常发生,如果冲突再回滚
11.2 封锁
封锁是实现并发控制的一个重要技术
排他锁:写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁为止。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
共享锁:读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁为止,这就保证了其他事务可以读A,但在T上释放A的S锁之前不能对A做任何修改。
11.3 封锁协议
- 一级封锁协议:事务T在修改数据R前必须先对其加X锁,直到事务结束才释放。只能保证不发生丢失修改。
- 二级封锁协议:在一级封锁协议基础上,增加事务T在读取数据R前必须先对其加S锁,读完后即可释放S锁。增加保证了不发生读“脏”数据
- 三级封锁协议:在一级封锁协议的基础上,增加事务T在读取数据R前必须先对其加S锁,直到事务结束才释放进一步防止了不可重复读
封锁协议级别越高,一致性程度越高
11.4 活锁和死锁
11.4.1 活锁
避免活锁的简单方法是采用先来先服务的策略。
11.4.2 死锁
死锁的预防
- 一次封锁法:每个事务必须一次将所有要使用的数据全部加锁。
- 缺点:降低了系统的并发度。而且数据是不断变化的,很难事先精确确定每个事务所要封锁的数据对象。
- 顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实现封锁。
- 缺点:封锁的数据对象极多且在不断变化。事务的封锁请求随着事务的执行而动态地决定,很难事先确定。
- 缺点:封锁的数据对象极多且在不断变化。事务的封锁请求随着事务的执行而动态地决定,很难事先确定。
死锁的诊断与解除
- 超时法:实现简单,用得最多
缺点:可能误判。若时限太长不能及时发现 - 等待图法:精确判断死锁
解除死锁的方法:选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有的锁,使其他事务得以继续运行下去。但实际上是很难的,通常随便选定一个回滚,因为代价很难判断。
11.5 并发调度的可串行性
11.5.1 可串行化调度
定义:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同,称这种调度策略为可串行化调度
可串行性(serializability)是并发事务正确调度的准则
现在有两个事务,分别包含:
事务T1:读B;A=B+1;写回A
事务T2:读A;B=A+1;写回B
假设A、B的初值均为2,按T1->T2次序执行结果为A=3,B=4;按T2->T1z次序执行结果为B=3,A=4;
系统对并行事务中并行操作的调度是随机的,而不同的调度可能会产生不同的结果。
所有事务串行起来的调度策略一定是正确的调度策略
11.5.2 冲突可串行化调度
冲突操作:不同的事务对同一个数据的读写操作和写写操作
不同事务的冲突操作和同一事务的两个操作是不能交换的
一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度Sc’,如果Sc’是串行的,调度Sc为冲突可串行化的调度。若一个调度是冲突可串行化,则一定是可串行化的调度。
在实际系统中,通常用优先图。一个事务对应一个节点,两个事务间有有向边(Ti->Tj)当且仅当Ti在Tj前执行,意味着他们之间有冲突不能交换。图没有环意味着冲突可串行化。
11.6 两段锁协议(保证并发调度的正确性)
定义:所有事务必须分两个阶段对数据项进行加锁和解锁
- 在对任何数据进行读写操作之前,首先要申请并获得对该数据的封锁
- 在释放一个封锁之后,事务不再申请和获得任何其他封锁
事务分为两个阶段:
1. 获得封锁,也称为扩展阶段,可以申请获得任何数据项上的任何类型的锁,不能释放任何锁
2. 释放封锁,也称为收缩阶段,可以释放任何锁,但是不能申请任何锁
可以证明,若并发执行的所有事务均遵守两段锁协议,则对这些事务的任何并发调度策略都是可串行化的(充分不必要条件),但是可能导致死锁
11.7 封锁的粒度
封锁粒度:封锁对象的大小,包括逻辑单元(属性值、关系……)和物理单元(页、物理记录……)
封锁粒度与系统的并发度和并发控制的开销密切相关
- 粒度大,开销小,并发度小
- 粒度小,开销大,并发度大
11.7.1 多粒度封锁
多粒度树:根是整个数据库,表示最大的数据粒度,叶节点表示最小的数据粒度
多粒度封锁协议:对一个结点加锁意味着这个结点的所有后代结点也被加以相同类型的锁
- 显式封锁:应事务的要求直接加到数据对象上的锁
- 隐式封锁:该数据对象没有被独立加锁,是由于其上级结点加锁而使该数据对象加上了锁
加锁过程:对R1加X锁,需要搜索其上级结点数据库、R1本身和R1的下级结点,如果其中某一个数据对象已经加了不相容锁,则必须等待。开销太大,解决方法是意向锁
11.7.2 意向锁
意向锁:如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层加意向锁
- 意向共享锁(IS锁):表示它的后代结点拟加X锁
- 意向排他锁(IX锁)
- 共享意向排他锁(SIX锁):对它加S锁,再加IX锁
加锁方法:从根开始对它的上层结点加意向锁,再对数据对象加锁。释放封锁则自下而上地进行
优点:提高了系统的并发度,减少了加锁和解锁的开销。
习题:
1、在数据库中为什么要并发控制?
答:数据库是共享资源,通常有许多个事务同时在运行。当多个事务并发地存取数据库时就会产生同时读取和/或修改同一数据的情况。若对并发操作不加控制就可能会存取和存储不正确的数据,破坏数据库的一致性。所以数据库管理系统必须提供并发控制机制。
2 .并发操作可能会产生哪几类数据不一致?用什么方法能避免各种不一致的情况?
答:并发操作带来的数据不一致性包括三类:丢失修改、不可重复读和读“脏’数据。
- ( l )丢失修改(lost update ) 两个事务 Tl 和T2读入同一数据并修改,T2提交的结果破坏了(覆盖了) Tl 提交的结果,导致 Tl 的修改被丢失。
- ( 2 )不可重复读( Non 一 Repeatable Read ) 不可重复读是指事务 Tl 读取数据后,事务几执行更新操作,使 Tl 无法再现前一次读取结果。
- ( 3 )读“脏”数据( Dirty Read ) 是指事务 Tl 修改某一数据,并将其写回磁盘,事务几读取同一数据后, Tl 由于某种原因被撤销,这时 Tl 已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为“脏”数据,即不正确的数据。
- 避免不一致性的方法和技术就是并发控制。最常用的技术是封锁技术。也可以用其他技术,例如在分布式数据库系统中可以采用时间戳方法来进行并发控制。
3 .什么是封锁?基本的封锁类型有几种?试述它们的含义。
答:封锁就是事务 T 在对某个数据对象例如表、记录等操作之前,先向系统发出请求,对其加锁。加锁后事务 T 就对该数据对象有了一定的控制,在事务 T 释放它的锁之前,其他的事务不能更新此数据对象。封锁是实现并发控制的一个非常重要的技术。
基本的封锁类型有两种:排它锁( Exclusive Locks ,简称 x 锁)和共享锁 ( Share Locks,简称 S 锁)。排它锁又称为写锁。若事务 T 对数据对象 A 加上 X 锁,则只允许 T 读取和修改 A ,其他任何事务都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁。这就保证了其他事务在 T 释放 A 上的锁之前不能再读取和修改 A 。共享锁又称为读锁。若事务 T 对数据对象 A 加上 S 锁,则事务 T 可以读 A但不能修改 A ,其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁。这就保证了其他事务可以读 A ,但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改。
4 .如何用封锁机制保证数据的一致性?
答: DBMS 在对数据进行读、写操作之前首先对该数据执行封锁操作,例如下图中事务 Tl 在对 A 进行修改之前先对 A 执行 xock ( A ) ,即对 A 加 x 锁。这样,当几请求对 A 加 x 锁时就被拒绝,几只能等待 Tl 释放 A 上的锁后才能获得对 A 的 x 锁,这时它读到的 A 是 Tl 更新后的值,再按此新的 A 值进行运算。这样就不会丢失 Tl 的更新。
DBMS 按照一定的封锁协议,对并发操作进行控制,使得多个并发操作有序地执行,就可以避免丢失修改、不可重复读和读“脏’夕数据等数据不一致性。
5 .什么是活锁?什么是死锁?
如果事务 Tl 封锁了数据 R ,事务几又请求封锁 R ,于是几等待。几也请求封锁 R ,当 Tl 释放了 R 上的封锁之后系统首先批准了几的请求,几仍然等待。然后几又请求封锁 R ,当几释放了 R 上的封锁之后系统又批准了几的请求 … … 几有可能永远等待,这就是活锁的情形。活锁的含义是该等待事务等待时间太长,似乎被锁住了,实际上可能被激活。如果事务 Tl 封锁了数据 Rl ,几封锁了数据凡,然后 Tl 又请求封锁几,因几已封锁了几,于是 Tl 等待几释放几上的锁。接着几又申请封锁 Rl ,因 Tl 已封锁了 Rl ,几也只能等待 Tl 释放 Rl 上的锁。这样就出现了 Tl 在等待几,而几又在等待 T }的局面, T }和几两个事务永远不能结束,形成死锁。
6 .试述活锁的产生原因和解决方法。
答:活锁产生的原因:当一系列封锁不能按照其先后顺序执行时,就可能导致一些事务无限期等待某个封锁,从而导致活锁。避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。
7.请给出检测死锁发生的一种方法,当发生死锁后如何解除死锁?
答:数据库系统一般采用允许死锁发生, DBMS 检测到死锁后加以解除的方法。 DBMS 中诊断死锁的方法与操作系统类似,一般使用超时法或事务等待图法。超时法是:如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。超时法实现简单,但有可能误判死锁,事务因其他原因长时间等待超过时限时,系统会误认为发生了死锁。若时限设置得太长,又不能及时发现死锁发生。 DBMS 并发控制子系统检测到死锁后,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有锁,使其他事务得以继续运行下去。当然,对撤销的事务所执行的数据修改操作必须加以恢复。
8.什么样的并发调度是正确的调度?
答:可串行化( Serializable )的调度是正确的调度。可串行化的调度的定义:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行执行它们时的结果相同,称这种调度策略为可串行化的调度。
9 .设 Tl ,几,几是如下的 3 个事务:
Tl :A : = A + 2 ;
T2:A : = A * 2 ;
T3:A : = A **2 ; ( A <-A*A)
设 A 的初值为 0 。
( l )若这 3 个事务允许并行执行,则有多少可能的正确结果,请一一列举出来。
答 :A 的最终结果可能有 2 、 4 、 8 、 16 。因为串行执行次序有 Tl T2T3、 Tl T3T2、T2T1T3、T2T3Tl 、T3T1T2、T3T2 Tl 。对应的执行结果是 16 、 8 · 4 · 2 · 4 · 2 。
( 2 )请给出一个可串行化的调度,并给出执行结果
答:
最后结果 A 为 16 ,是可串行化的调度。
( 3 )请给出一个非串行化的调度,并给出执行结果。
答:
最后结果 A 为 0 ,为非串行化的调度。
( 4 )若这 3 个事务都遵守两段锁协议,请给出一个不产生死锁的可串行化调度。
答:
( 5 )若这 3 个事务都遵守两段锁协议,请给出一个产生死锁的调度。
答:
10.试证明,若并发事务遵守两段锁协议,则对这些事务的并发调度是可串行化的。
证明:首先以两个并发事务 Tl 和T2为例,存在多个并发事务的情形可以类推。根据可串行化定义可知,事务不可串行化只可能发生在下列两种情况:
( l )事务 Tl 写某个数据对象 A ,T2读或写 A ;
( 2 )事务 Tl 读或写某个数据对象 A ,T2写 A 。
下面称 A 为潜在冲突对象。
设 Tl 和T2访问的潜在冲突的公共对象为{A1,A2 … , An }。不失一般性,假设这组潜在冲突对象中 X =(A 1 , A2 , … , Ai }均符合情况 1 。 Y ={A i + 1 , … , An }符合所情况( 2 )。
VX ∈ x , Tl 需要 XlockX ①
T2 需要 Slockx 或 Xlockx ②
1 )如果操作 ① 先执行,则 Tl 获得锁,T2等待
由于遵守两段锁协议, Tl 在成功获得 x 和 Y 中全部对象及非潜在冲突对象的锁后,才会释放锁。
这时如果存在 w ∈ x 或 Y ,T2已获得 w 的锁,则出现死锁;否则, Tl 在对 x 、 Y 中对象全部处理完毕后,T2才能执行。这相当于按 Tl 、T2的顺序串行执行,根据可串行化定义, Tl 和几的调度是可串行化的。
2 )操作 ② 先执行的情况与( l )对称因此,若并发事务遵守两段锁协议,在不发生死锁的情况下,对这些事务的并发调度一定是可串行化的。证毕。
12 .举例说明,对并发事务的一个调度是可串行化的,而这些并发事务不一定遵守两段锁协议。
13 .为什么要引进意向锁?意向锁的含义是什么?
答:引进意向锁是为了提高封锁子系统的效率。该封锁子系统支持多种封锁粒度。原因是:在多粒度封锁方法中一个数据对象可能以两种方式加锁 ― 显式封锁和隐式封锁。因此系统在对某一数据对象加锁时不仅要检查该数据对象上有无(显式和隐式)封锁与之冲突,还要检查其所有上级结点和所有下级结点,看申请的封锁是否与这些结点上的(显式和隐式)封锁冲突,显然,这样的检查方法效率很低。为此引进了意向锁。意向锁的含义是:对任一结点加锁时,必须先对它的上层结点加意向锁。例如事务 T 要对某个元组加 X 锁,则首先要对关系和数据库加 ix 锁。换言之,对关系和数据库加 ix 锁,表示它的后裔结点 ― 某个元组拟(意向)加 X 锁。引进意向锁后,系统对某一数据对象加锁时不必逐个检查与下一级结点的封锁冲突了。例如,事务 T 要对关系 R 加 X 锁时,系统只要检查根结点数据库和 R 本身是否已加了不相容的锁(如发现已经加了 ix ,则与 X 冲突),而不再需要搜索和检查 R 中的每一个元组是否加了 X 锁或 S 锁。
14 .试述常用的意向锁: IS 锁、IX锁、 SIX 锁,给出这些锁的相容矩阵。
答: IS锁:如果对一个数据对象加 IS 锁,表示它的后裔结点拟(意向)加 S 锁。例如,要对某个元组加 S 锁,则要首先对关系和数据库加 IS 锁
IX 锁:如果对一个数据对象加 ix 锁,表示它的后裔结点拟(意向功口 X 锁。例如,要对某个元组加 X 锁,则要首先对关系和数据库加 ix 锁。
SIX 锁:如果对一个数据对象加 SIX 锁,表示对它加 S 锁,再加 IX 锁,即 SIX = S + IX 。
相容矩阵: