封锁
- 封锁就是事务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锁
封锁类型的相容性
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之前必须先对其加X锁,直到事务结束才释放
- 二级封锁协议
- 一级封锁协议加上事务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,事务起初要求封锁数据对象BCE,但当它封锁了后,发现还需要封锁A,这样就破坏了封锁顺序
- 维护成本高
- 一次封锁法
- 结论
- 在操作系统中广为采用的预防死锁的策略并不很适合数据库的特点
- DBMS在解决死锁的问题上更普遍采用的是诊断并解除死锁的方法
- 死锁的诊断与解除
- 诊断方法
- 有DBMS的并发控制子系统定期监测系统中是否存在死锁,一旦检测到死锁,就要设法解除
- 超时法
- 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
- 优点:实现简单
- 缺点:有可能误判死锁、时限若设置的太长,死锁发生后不能及时发现
- 等待图法
- 用事务等待图动态反映所有事务的等待情况
- 事务等待图是一个有向图
- T为结点的集合,每个结点表示正运行的事务
- U为边的集合,每条边表示事务等待的情况
- 若等待,则之间划一条有向边,从指向
- 并发控制子系统周期性地(如每个1min)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁
- 用事务等待图动态反映所有事务的等待情况
- 死锁的解除
- 解决死锁
- 选择一个处理死锁代价最小的事务,将其撤销
- 释放此事务持有的所有锁,使其它事务能继续运行下去
- 解决死锁
- 诊断方法
- 死锁的预防