一文搞懂如何使用封锁来实现并发控制

前言

 随着数据库应用的不断发展,数据规模逐渐升级,为了提高效率。往往会将多个事务并发的执行。而多个事务并发可能会同时存取同一数据,产生数据不一致的情况。比如“脏”读,不可重复读等。所以数据库管理系统(简称DBMS),必须提供并发控制机制。使得并发的事务在冲突时串行化执行。这种调度称为可串行化调度。
  并发控制主要有两种方式,封锁和时间戳。我们先来讨论用封锁的方式来保证事务并发控制中的一致性问题。封锁方式是基于各种锁来进行并发控制。在封锁机制中,当多个事务同时访问同一数据时,应对其进行封锁请求的授予或等待。而加锁可能带来死锁问题,所以加锁是要进行死锁检测,一旦被发现死锁则回滚代价最小的事务。

数据库并发控制

 数据库管理系统(DBMS)中的并发控制是为了确保在多个事务同时存取数据库中的同一数据时,不破坏事务的一致性和隔离性。不进行并发控制可能会造成以下问题。

第一类丢失更新(回滚丢失 lost update)

 当两个事务更新相同的元素,如果一个事务被提交,而另一个事务却被回滚,那么会连通先提交的事务所做的更新也被回滚。如图所示:

时间 取款事务 转账事务
T1 开始事务
T2 开始事务
T3 查询余额1000元
T4 查询余额1000元
T5 取出100元,余额为900元
T6 提交事务
T7 汇出100元,余额为900元
T8 回滚事务
T9 丢失更新(银行损失100元) 余额恢复为1000元

第二类丢失更新(两次更新 second lost update)

 无法重复读取的特例。有两个并发事务同时读取同一行数据,其中一个会对它进行修改提交,而另一个也进行修改提交。这也会造成第一次写操作失败。如图所示:

时间 取款事务 转账事务
T1 开始事务
T2 开始事务
T3 查询余额为1000元
T4 查询余额为1000元
T5 取出100元,余额为900元
T6 提交事务
T7 转入100元,余额为1100元
T8 丢失更新,银行损失100元

封锁

 所谓封锁就是事务T在对某个数据对象(表、记录等)操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该数据对象有了一定的控制。基本的封锁类型有两种:排他锁(Exclusive Locks,简称X锁)和共享锁(Share Locks,简称S锁)。排它锁又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。共享锁又称读锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能在对A加S锁,而不能加X锁,直到T释放A上的S锁。
 排它锁与共享锁的控制方式可以用如下图的相容矩阵表示,相容性矩阵中对应的每种封锁方式有一行和一列。行对应于数据库元素A上某一事务已经持有的锁,列对应于A上申请的锁。

S X
S Y N
X N N

Y=Yes,表示申请的锁请求与已持有的锁是相容的请求
N=No,表示申请的锁请求与已持有的锁是不相容的请求

封锁协议

 在运用X锁和S锁对对象加锁时,还需要约定一些规则,例如何时申请X锁或S锁、持锁时间、何时释放等。称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。封锁协议的规则是对隔离级别的实现,在封锁协议中的丢失更新是指第二类丢失更新。

一级封锁协议

 事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。而这级封锁协议是不对读数据加锁的。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
 一级封锁协议可以防止丢失更新,并保证事务T是可恢复的。使用一级封锁协议可以解决丢失更新问题,但它不能保证可重复读和不读“脏”数据。
 这种情况符合SQL99中对读未提交隔离级别的效果,可以认为该级锁协议对应的就是读未提交。

二级封锁协议

 一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后立即释放S锁。
  二级封锁协议除防止了丢失更新,还可以进一步防止读“脏”数据。但在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。
这种情况符合SQL99中对读已提交隔离级别的效果,可以认为该级锁协议对应的就是读已提交。

三级封锁协议

 一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。
 三级封锁协议除防止了丢失修改和不读“脏”数据外,还进一步防止了不可重复读,但并没有完全防止幻读,因为该级协议中并未对可能不存在的数据加锁,而并发事务中可能会插入新数据导致前面的事务产生幻读。
 这种情况符合SQL99中对可重复读隔离级别的效果,可以认为该级锁协议对应的就是可重复读。
 如果在共享排他锁的基础上再加入范围锁,则应用三级封锁协议也能防止幻读,即达到了SQL99中对可串行化隔离级别的效果。

两阶段封锁协议

 在对任何数据读、写操作之前,事务首先或得该数据的封锁,并且不能释放任何锁。在释放一个封锁之后,事务不再获得任何封锁。
例如:
 事务T1的封锁序列SlockA->SlockB->XlockC->UnlockC->UnlockB->UnlockA;
 事务T2的封锁序列Slock A ->Unlock A -> Slock B -> Xlock C ->Unlock C ->Unlock B。
 事务T1遵守两阶段锁协议,而事务T2不遵守两阶段锁协议。
 两阶段封锁协议保证了事务的串行化调度,但同时,一个事务的失败可能会引起一连串事务的回滚。为避免这种情况的发生,需要进一步加强对两阶段封锁协议的控制:

  1. 严格两阶段封锁协议:除了要求封锁是两阶段之外,还要求事务持有的所有排它锁必须在事务提交之后方可释放。这个要求保证未提交事务所写的任何数据,在该事务提交之前均以排它锁封锁,防止其他事务读取这些数据。
  2. 强两阶段封锁协议:除了要求封锁是两阶段之外,还要求事务提交之前不得释放任何锁(包括共享锁)。

封锁产生的问题-死锁

 以上所有的封锁协议都不足以保证不会发生死锁,因为死锁是两个或者多个事务都各自锁定一些数据库元素,然后又都请求对已被其他事务封锁的数据对象封锁,从而出现死等的情况。
在这里插入图片描述
 事务T1先访问数据库元素R1并且被授予了对R1的封锁,同时需要访问数据库元素R2;
 事务T2先访问数据库元素R2并且被授予了对R2的封锁,同时需要访问数据库元素R1。
 这时T1由于T2已经锁定R2,则等待T2释放R2才能继续,同样T2要等待T1释放R1才能继续,导致了死锁。

猜你喜欢

转载自blog.csdn.net/songguangfan/article/details/107458270
今日推荐