并发控制
并发控制机制的任务
- 对并发操作进行正确调度
- 保证事务的隔离性
- 保证数据库的一致性
事务是并发控制的基本单位
并发操作带来的数据不一致性
①丢失修改(写-写)
两个事务T1和T2读入同一数据并修改,T2的提交结果破坏了T1提交的结果,导致T1的修改被丢失。
②不可重复读(读-更新)
不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。
不可重复读包括三种情况:
- 事务T1读取某一数据后,
事务T2对其做了修改
,当事务T1再次读该数据时,得到与前一次不同的值 - 事务T1按一定条件从数据库中读取了某些数据记录后,
事务T2删除了其中部分记录
,当T1再次按相同条件读取数据时,发现某些记录神秘地消失了。 - 事务T1按一定条件从数据库中读取某些数据记录后,
事务T2插入了一些记录
,当T1再次按相同条件读取数据时,发现多了一些记录。
后两种情况也称幻影
现象
③脏数据(修改-读)
- 事务T1修改某一数据,并将其写回磁盘
- 事务T2读取同一数据后,T1由于某种原因被撤销
- 这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致
- T2读到的数据就为“脏”数据,即不正确的数据
并发控制的主要技术
- 封锁(Locking)
- 时间戳(Timestamp)
- 乐观控制法
- 多版本并发控制(MVCC)
封锁
- 排它锁(Exclusive Locks,简记为X锁)
- 共享锁(Share Locks,简记为S锁)
锁的相容矩阵
封锁协议
一级封锁协议
事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。
使用一级封锁协议解决丢失修改
问题
读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读“脏”数据。
二级封锁协议
一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后
即可释放S锁。
使用二级封锁协议解决“读”脏数据
问题
由于读完数据后即可释放S锁,所以它不能保证可重复读。
三级封锁协议
一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束
才释放。
使用三级封锁协议解决不可重复读
问题
三级协议主要区别
活锁和死锁
如下图,T2一直在等待R,但是理论上可能出现系统,一直没有把权限给它的时候,这时候就出现了死锁
。
避免活锁:采用先来先服务的策略
- 当多个事务请求封锁同一数据对象时
- 按请求封锁的先后次序对这些事务排队
- 该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁
T1一直在等待T2释放R2上的锁,而T2也一直在等待T1释放R1上的锁,双方一直等待,形成死锁
死锁的预防
一次封锁法
要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行
存在的问题
- 难于事先精确确定封锁对象 数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象。
- 解决方法:将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度。
顺序封锁法
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
存在的问题
- 维护成本 数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作而不断地变化,要维护这样的资源的封锁顺序非常困难,成本很高。
- 难以实现 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁 。
死锁的诊断
超时法
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
- 优点:实现简单
- 缺点
- 有可能误判死锁
- 时限若设置得太长,死锁发生后不能及时发现
等待图法
并发控制子系统周期性地(比如每隔数秒)生成事务等待图,检测事务。如果发现图中存在回路,则表示系统中出现了死锁。
解除死锁
- 选择一个处理死锁代价最小的事务,将其撤消
- 释放此事务持有的所有的锁,使其它事务能继续运行下去