并发控制——封锁、活锁和死锁

封锁

  • 封锁就是事务T在对某个数据对象(表、记录等)操作之前,先向系统发出请求,对其枷锁
  • 加锁后事务T就对该数据对象有了一定的控制,在事务T释放它的锁之前,其它的事务不能更新此数据对象
  • 封锁是实现并发控制的一个非常重要的技术

封锁类型

  • 基本封锁类型
    • 排它锁(eXclusive lock,简称X锁)
    • 共享锁(Share lock,简称S锁)

排它锁

  • 排它锁又称为写锁X锁
    • 若事务T1对数据对象A加上X锁,则只允许T1读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T1释放A上的X锁

共享锁

  • 共享锁又称为读锁S锁
    • 若事务T1对数据对象A加上S锁,则其它事务只能再对A加S锁,而不能加X锁,直到T1释放A上的S锁

封锁类型的相容性

T_{1}
T_{2} X S -
X N N Y
S N Y Y
- Y Y Y

Y=Yes,相容的请求
N=No,不相容的请求

封锁粒度

  • X锁S锁都是加在某一个数据对象上的
  • 封锁的数据对象可以是逻辑单元
    • 属性值、属性值集合、元组、关系、索引项、整个索引、整个数据库等
  • 封锁的数据对象也可以是物理单元
    • 页(数据页或索引页)、块等
  • 封锁对象可以是大或者很小
    • 对整个数据库加锁
    • 对某个属性值加锁
  • 封锁对象的大小称为封锁的粒度
  • 封锁粒度与系统的并发度和并发控制的开销密切相关
封锁的粒度 被封锁的对象 并发度 系统开销
  • 选择封锁粒度时必须同时考虑开销并发度两个因素,进行权衡,以求的最优的效果

封锁粒度一般原则

  • 需要处理大量元组的用户事务
    • 关系为封锁单元
  • 需要处理多个关系的大量元组的用户事务
    • 数据库为封锁单位
  • 只处理少量元组的用户事务
    • 元组为封锁事务

封锁协议

  • 在运用X锁和S锁数据对象加锁时,需要约定一些规则,这些规则为封锁协议(Locking Protocol)
    • 何时申请X锁或S锁
    • 持锁时间
    • 何时释放
  • 两种封锁协议
    • 三级封锁协议——保证数据一致性
    • 两段锁协议——保证并行调度可串行型

三级封锁协议

  • 一级封锁协议
    • 事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放
      • 正常结束(commit)
      • 非正常结束(rollback)
    • 一级封锁协议可防止丢失修改,并保证事务T是可恢复的
    • 在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所有它不能保证可重复读和不读“脏”数据

 

  • 二级封锁协议
    • 一级封锁协议加上事务T在读取数据R之前必须先对其加S锁读完后即释放S锁
    • 二级封锁协议可以防止丢失修改读“脏”数据

 

  • 三级封数据锁协议
    • 一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放
    • 三级封锁协议可防止丢失修改读“脏”数据不可重复读

三级协议的主要区别

什么操作需要申请封锁以及何时释放锁(持锁时间)

X锁 S锁 一致性保证
操作结束释放 事务结束释放 操作结束释放 事务结束释放 不丢失修改 不读“脏”数据 可重复读
一级封锁协议
二级封锁协议
三级封锁协议

两段锁协议

  • 可串行型是并行调度正确性的唯一准则
  • “两段”锁的含义
    • 在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁
    • 在释放一个封锁之后,事务不再获得任何其他封锁
  • 事务分为两个阶段
    • 第一阶段是获得封锁,称为扩展阶段
    • 第二阶段是释放封锁,称为收缩阶段
  • 事务1的封锁序列:SlockA...SlockB...XlockC...UnlockB...UnlockA...UnlockC 遵守2PL
  • 事务2的封锁序列:SlockA...UnlockA...SlockB...XlockC...UnlockC...UnlockB 不遵守2PL
  • 并行执行的所有事务均遵守两段锁协议,则对这些事务的所有并行调度策略都是可串行化的
  • 所有遵守两段锁协议的事务,并行执行的结果一定是正确的
  • 事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件
  • 即可串行化的调度中,不一定所有事务都必须符合两段锁协议

死锁和活锁

  • 封锁技术可以有效地解决并行操作的一致性问题
  • 但也带来了一些新的问题
    • 死锁
    • 活锁

活锁

  • 什么是活锁
    • 系统可能使某个事务永远处于等待状态,得不到封锁的机会这种现象称为“活锁”

  • 如何避免活锁 
    • 采用先来先服务的策略
      • 当多个事务请求封锁同一个数据对象时,封锁子系统按请求封锁的先后次序对这些事务排队
      • 该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁

死锁

  • 什么是死锁
    • 系统中有两个或两个以上的事务都处于等待状态,并且每个事务都在等待其中另一个事务解除封锁,它才能继续执行下去,结果造成任何一个事务都无法继续执行,这种现象称系统进入了“死锁”

  •  解决死锁的两类方法
    • 死锁的预防
      • 产生死锁的原因是两个或多个事务都已经封锁了一些数据对象,然后又都请求对已为其它事务封锁的数据对象加锁,从而出现死等待
      • 预防死锁的发生就是要破坏产生死锁的条件
      • 预防死锁的方法
        • 一次封锁法
          • 一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行
          • 一次封锁法存在的问题:
            • 降低并发度
              • 一次就将以后要用到全部数据加锁,势必扩大了封锁的范围从而降低了系统的并发度
            • 难于事先精确确定封锁对象
              • 数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以难事先精确地确定每个事务所要封锁的数据对象
        • 顺序封锁发
          • 顺序封锁是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁
          • 顺序封锁法存在的问题:
            • 维护成本高
              • 数据库系统中可封锁的数据对象极其众多,并且随数据的插入、删除等操作而不断地变化,要维护这样极多而且变化的资源的封锁顺序非常困难,成本很高
            • 难于实现
              • 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也很难按规定的顺序去施加封锁
              • 例子:规定数据对象的封锁顺序为ABCDE,事务T_{3}起初要求封锁数据对象BCE,但当它封锁了后,发现还需要封锁A,这样就破坏了封锁顺序​​​​​​
    • 结论
      • 在操作系统中广为采用的预防死锁的策略并不很适合数据库的特点
      • DBMS在解决死锁的问题上更普遍采用的是诊断并解除死锁的方法
    • 死锁的诊断与解除
      • 诊断方法
        • 有DBMS的并发控制子系统定期监测系统中是否存在死锁,一旦检测到死锁,就要设法解除
        • 超时法
          • 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
          • 优点:实现简单
          • 缺点:有可能误判死锁、时限若设置的太长,死锁发生后不能及时发现
        • 等待图法
          • 用事务等待图动态反映所有事务的等待情况
            • 事务等待图是一个有向图G= \left ( T,U \right )
            • T为结点的集合,每个结点表示正运行的事务
            • U为边的集合,每条边表示事务等待的情况
            • T_{1}等待T_{2},则T_{1},T_{2}之间划一条有向边,从T_{1}指向T_{2}
          • 并发控制子系统周期性地(如每个1min)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁
      • 死锁的解除
        • ​​​​​​​解决死锁
          • ​​​​​​​选择一个处理死锁代价最小的事务,将其撤销
          • 释放此事务持有的所有锁,使其它事务能继续运行下去

猜你喜欢

转载自blog.csdn.net/qq_38889101/article/details/124304989