数据库并发控制

319 阅读5分钟

并发控制

并发控制机制的任务

  • 对并发操作进行正确调度
  • 保证事务的隔离性
  • 保证数据库的一致性

事务是并发控制的基本单位

并发操作带来的数据不一致性

①丢失修改(写-写)

两个事务T1和T2读入同一数据并修改,T2的提交结果破坏了T1提交的结果,导致T1的修改被丢失。

1640238897460.png

②不可重复读(读-更新)

不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。

不可重复读包括三种情况:

  • 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值
  • 事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录神秘地消失了。
  • 事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

后两种情况也称幻影现象

1640239408261.png

③脏数据(修改-读)

  • 事务T1修改某一数据,并将其写回磁盘
  • 事务T2读取同一数据后,T1由于某种原因被撤销
  • 这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致
  • T2读到的数据就为“脏”数据,即不正确的数据

1640239495485.png

并发控制的主要技术

  • 封锁(Locking)
  • 时间戳(Timestamp)
  • 乐观控制法
  • 多版本并发控制(MVCC)

封锁

  • 排它锁(Exclusive Locks,简记为X锁)
  • 共享锁(Share Locks,简记为S锁)

锁的相容矩阵

1640239587041.png

封锁协议

一级封锁协议

事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。

使用一级封锁协议解决丢失修改问题

image.png

读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读“脏”数据。

二级封锁协议

一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁。

使用二级封锁协议解决“读”脏数据问题

image.png

由于读完数据后即可释放S锁,所以它不能保证可重复读。

三级封锁协议

一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。

使用三级封锁协议解决不可重复读问题

image.png

三级协议主要区别

1640239843177.png

活锁和死锁

如下图,T2一直在等待R,但是理论上可能出现系统,一直没有把权限给它的时候,这时候就出现了死锁

1640239989280.png

避免活锁:采用先来先服务的策略

  • 当多个事务请求封锁同一数据对象时
  • 按请求封锁的先后次序对这些事务排队
  • 该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁

T1一直在等待T2释放R2上的锁,而T2也一直在等待T1释放R1上的锁,双方一直等待,形成死锁

1640240160455.png

死锁的预防

一次封锁法

要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行

存在的问题

  • 难于事先精确确定封锁对象 数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象。
  • 解决方法:将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度。

顺序封锁法

顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。

存在的问题

  • 维护成本 数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作而不断地变化,要维护这样的资源的封锁顺序非常困难,成本很高。
  • 难以实现 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁 。

死锁的诊断

超时法

如果一个事务的等待时间超过了规定的时限,就认为发生了死锁

  • 优点:实现简单
  • 缺点
    • 有可能误判死锁
    • 时限若设置得太长,死锁发生后不能及时发现

等待图法

并发控制子系统周期性地(比如每隔数秒)生成事务等待图,检测事务。如果发现图中存在回路,则表示系统中出现了死锁。

1640240650365.png

解除死锁

  • 选择一个处理死锁代价最小的事务,将其撤消
  • 释放此事务持有的所有的锁,使其它事务能继续运行下去