一、引言
1、死锁是因采用封锁技术实现并发控制而产生的一种运行事务被阻塞或等待的现象
2、如果利用严格两阶段封锁协议来解决我们前面提到的“更新丢失”这种数据不一致问题,非串行调度中的事务T1首先获得数据对象X上的读锁并开始执行,随后事务T2也获得数据对象X上的读锁开始执行。当事务T1想对X写入新值时,则需将读锁升级为写锁,但其申请的数据对象X上的写锁与事务T2当前所持有的读锁不相容,事务T1申请的写锁得不到满足,事务T1需要等待,当事务T2想对数据对象X写入新值时,也需将读锁升级为写锁,但其申请的数据对象X上的写锁,也与事务T1当前所持有的读锁不相容,事务T2也不能获得数据对象X上的写锁,事务T2也需要等待,这就造成两个并发事务都不能继续执行,并相互等待对方释放锁,进入死锁状态
3、根据发生死锁时并发事务的等待状态的不同,可将死锁细分为“活死锁”,简称“活锁”和“死死锁”,简称“死锁”,处理死锁的方法包括预防和检测
二、活锁
活锁是指并发事务中有部分事务因封锁请求得不到满足而长期处于等待的状态,但其他事务仍可以继续运行下去。处于长期等待状态的事务也称作被“饿死”
比如这里给出的4个并发事务的封锁情况,事务T1首先获得数据对象A上的读锁,事务T2在事务T1没有释放锁前,对数据对象A的写锁申请被拒绝,T2处于等待状态,但事务T3、事务T4对数据对象A的读锁申请可依次得到满足,能够继续执行,导致事务T1释放锁后事务T2仍然处于等待状态,等待其他事务释放锁,形成活锁
三、活锁的预防
避免发生活锁的简单方法是采用先来先服务的策略,当多个事务申请封锁同一数据对象时,按申请封锁的先后次序对这些事务排队,该数据对象上的锁一旦释放,申请队列的第一个事务首先获得锁
四、死锁
死死锁是指并发事务中的事务各自拥有某数据库对象上的“锁”,并去申请其他事务对某数据库对象所持有的“锁”,因申请得不到满足而产生的循环等待状态。
比如前面提到的利用严格两阶段封锁协议解决“更新丢失”问题中的事务T1、T2,分别等待对方释放锁,同时又都持有对方所申请的锁,两个事务将一直等待下去,谁也不能完成,形成死锁
利用两阶段进行并发控制,难免会造成并发事务相互间申请与另一个事务已获得的锁不相容的锁或多个事务在同一个数据对象上持有共享锁并都希望将锁升级的现象,从而导致出现死锁
五、死锁的预防和检测
1、因此,DBMS应能够对可能发生的死锁进行处理,处理死锁的方法大致分为两种
(1)一种是预防死锁,即对死锁加以预防,防止发生死锁。预防死锁发生的方法有
- 一次封锁法。该方法要求每个事务必须获得要访问的所有数据对象上的锁后才能开始执行,而不是先占有部分锁
- 顺序封锁法。该方法将数据对象按某种顺序排序,所有并发事务也按这个顺序申请数据对象上的锁,那么就不会由于相互等待所需要的锁而导致死锁
(2)一种是检测并解除死锁即允许死锁发生,但要及时检测并进行解除。检测并解除死锁的方法有
- 超时回滚法。即对事务的执行时间加以限制,如果某个事务的执行时间超过了这个限制,就认为其发生了死锁,将其回滚 。当一个事务因超时而回滚后,该事务将释放其持有的锁,其他事务则有可能申请到所需要的锁执行完成。比如,在一般事务的执行时间为几毫秒的数据库系统中,以1分钟为超时时限,则超时回滚的应是陷入死锁的事务,该方法实现简单,但仍有可能误判死锁,但超时时限设置得过长,则又不能及时发现死锁
2、DBMS常用的处理死锁的方法是事务等待图法,该方法不仅可预防死锁,也可对死锁进行检测
(1)事务等待图是一个由结点和边构成的有向图。G(T,U)
- 图中的每个结点表示正在运行的事务
- 图中的每条有向边表示一个事务在等待获取另一个事务释放其拥有的锁。
比如,在调度中,事务T2先读取数据对象A,可获得A上的读锁,然后事务T1要写数据对象A,则要申请A上的写锁,此时事务T1需要等待事务T2释放其已获取的读锁(r2(A),w1(A)),则在事务等待图中应存在结点T1和T2,并在结点T1和T2之间划一条从T1指向T2的有向边
(2)用事务等待图可动态反应系统中并发事务申请等待数据对象上的锁的情况
(3)若事务等待图中存在环路,如图中存在由红色有向边构成的有向环路,则表示系统中存在并发事务的循环等待情况,发生了死锁
(4)系统需周期地,比如每隔1min检测事务等待图,当系统检测到事务等待图中存在环路后,将选择环路中一个撤销该事务所需代价最小的事务。比如图中的事务T3,将其回滚,被撤销的事务将不再申请锁,且释放其持有的所有锁,原有环路中至少有一个事务,比如图中的事务T2可以执行不用再等待,该事务完成后又会释放其持有的锁,以此类推,原有环路中的其他事务也能陆续执行完成,解除死锁
(5)事务等待图可以用来在死锁形成后检测死锁,也可以用来预防死锁的形成。避免死锁的一种策略就是回滚所提请求将导致等待图中出现环路的任一事务
3、举例:
下面来看在并发事务的该调度执行过程中事务等待图的变化情况
首先事务T1可获得数据对象A上的读锁,读取对象A,然后事务T3可获得数据对象B上的读锁,读取对象B,事务T1又可获得数据对象C上的写锁,写对象C,事务T3又可获得数据对象D上的写锁,写对象D,此时均没有冲突操作,没有事务等待情况
但当事务T2要读数据对象C时,因事务T1已获得数据对象C上的写锁,则事务T2只能等事务T1释放锁,则在事务等待图中,会产生结点T2到结点T1的一条边
当事务T1要写数据对象B时,因事务T3已获得数据对象B上的读锁,则事务T1也只能等事务T3释放锁,则在事务等待图中,会产生结点T1到结点T3的一条边
当事务T4要写数据对象D时,因事务T3已获得数据对象D上的写锁,则事务T4也只能等事务T3释放锁,则在事务等待图中,会产生结点T4到结点T3的一条边
当事务T3要写数据对象A时,因事务T1已获得数据对象A上的读锁,则事务T3也只能等事务T1释放锁,则在事务等待图中会产生结点T3到结点T1的一条边。此时,在事务等待图中出现了环路,即事务T1、事务T3相互等待,发生死锁
可将所提请求将导致出现环路的事务T3回滚,释放其拥有的数据对象B和D上的锁,w1(B)和w4(D)操作可继续执行
或将事务T1回滚,释放其拥有的数据对象A和C上的锁,r2(C)、w3(A)操作可继续执行
六、小结
1、并发事务因竞争不到所需的共享锁资源,无论是长期等待处于饿死的状态,还是相互等待处于死锁的状态,均要造成系统性能的降低
2、大多数DBMS通过判断事务等待图是否形成环路来避免或解除死锁