两种基本封锁类型
封锁协议
活锁
死锁
多粒度封锁
意向锁
锁的强度
并发控制技术与前一篇提到的数据库恢复技术是主要的事务处理技术,同时并发控制机制和数据库恢复机制是数据库管理系统(DBMS)的重要组成部分。
在单处理机系统中,事务的并行执行实际上是这些并行事务轮流交叉运行。这种并行执行方式称为交叉并发方式(interleaved concurrency)。虽然单处理机系统中的并行事务并没有真正地(物理上)并行运行,但是减少了处理机和各设备的空闲时间,提高了系统的效率。
在多处理机系统中,当事务数小于处理机数时,每个处理机可以运行一个事务,实现真正意义上的并行运行。这种并行执行方式称为同时并发方式(simultaneous concurrency)。
当多个用户并发地操作数据库时,就有可能出现多个事务同时存取同一数据的情况,这种情况可能会破坏事务的一致性和数据库的一致性,所以数据库管理系统(DBMS)必须提供并发控制机制。并发控制机制是衡量一个DBMS性能的重要标志之一。
以下三类数据不一致性的主要原因是并发操作破坏了事务的隔离性。
1)丢失修改(lost update):
事务T1和T2先后读入同一数据并修改,T1先提交,T2后提交,T2提交的结果将覆盖T1的结果,导致T1的修改被丢失。
2)不可重复读(non-repeatable read):
事务T1读入某一数据后(未进行操作),T2对同一数据进行了操作(修改、插入、删除),当T1再次读入该数据时,得到与前一次不同的值(或者多了、少了某些记录)。这种不一致性与丢失修改的理想情况相反。
3)读“脏”数据(dirty read):
事务T1修改某一数据后将其写入磁盘,事务T2读入修改后的数据,此时T1由于某种原因被撤销(ROLLBACK),被T1修改的数据恢复原值,导致T2读入的数据与数据库中的数据不一致。
事务T在对某个数据对象,如表、记录等操作之前,需要先向系统发出加锁请求,在事务T释放它的锁之前,其他事务不能对此数据进行修改。
1)排他锁(exclusive locks,X锁):
排他锁又称为写锁。事务T对数据对象A加上X锁后,只允许事务T对A进行读取和修改,其他事务不能对A加任何类型的锁,直到T释放它的X锁为止。从而保证了其他事务在T释放A上的X锁之前不能对A进行读取和修改。
2)共享锁(share locks,S锁):
共享锁又称为读锁。事务T对数据对象A加上S锁后,事务T只能对A进行读取,而不能修改。其他事务可以继续对A加上S锁,但是不能加X锁,直到T释放它的S锁为止。从而保证了其他事务可以读取A,但是在T释放A上的S锁之前不能对A进行修改。
*需要注意的是,同一事务可以不断对某个数据对象加锁,不需要等锁的释放。
1)一级封锁协议:
定义:事务T在修改数据A前,必须先对A加X锁,直到事务结束才释放。因此X锁又被称为写锁,意为修改。
目的:解决“丢失修改”的不一致问题,即在下一个事务操作前,先把上一个事务的修改操作结束。
实现:事务T1的X锁→事务T2的X锁
事务T1对数据对象A加上X锁后,只有等事务T1(修改)结束,释放X锁,事务T2才能对A加锁并进行操作(读取、修改)。
2)二级封锁协议:
定义:在一级封锁协议的基础上,增加事务T在读取数据A前,必须先对A加S锁,读完后即可释放。因此S锁又被称为读锁,意为读取。
目的:在一级封锁协议的基础上,进一步解决“读‘脏’数据”的不一致问题,即在下一个事务读取前,先等上一个事务的撤销操作结束。
实现:事务T1的X锁→事务T2的S锁(读取数据后释放S锁)
事务T1对数据对象A加上X锁后,只有等事务T1(修改、撤销)结束,释放X锁,事务T2才能对A加锁并进行操作(读取)。
3)三级封锁协议:
定义:在一级封锁协议的基础上,增加事务T在读取数据A前,必须先对A加S锁,直到事务结束才释放(与二级的区别)。
目的:在二级封锁协议的基础上,进一步解决“不可重复读”的不一致问题,即在下一个事务修改前,先等上一个事务的重复读操作结束。
实现:事务T1的S锁(事务结束后释放S锁)→事务T2的X锁
事务T1对数据对象A加上S锁后,只有等事务T1(重复读取)结束,释放S锁,事务T2才能对A加X锁并进行操作(修改)。
和操作系统一样,封锁的方法可能引起活锁和死锁等问题。
根据事务的优先级顺序,可能会出现某个事务永远在等待封锁的情况,即事务T1封锁了数据对象A后,T2、T3陆续请求封锁,但是T1释放锁后,系统优先批准了T3的请求,T2仍然在等待。
最简单的解决方法就是先来先服务(FCFS),不考虑事务的优先级。
事务T1封锁了数据A,事务T2封锁了数据B,然后T1请求封锁B,与此同时T2也请求封锁A,但因为两个事务的请求都需要等待对方释放锁,这样就出现了永远在等待对方的死锁。
在数据库中,解决死锁问题主要有两类方法:预防和诊断解除。
1)预防:
1. 一次封锁法:每个事务一次将所有要使用的数据加锁,否则事务不能继续执行。
带来的问题:阻碍了其他事务对数据的利用,从而降低了系统的并发度。
2. 顺序封锁法:预先对数据规定一个封锁顺序,所有事务都按照这个顺序加锁,保证“先到先得”。
带来的问题:需要处理的信息太多,开销大,成本高。
2)诊断与解除:
1. 超时法:某个事务的等待时间超过规定时间,则系统判定为死锁。
带来的问题:规定时间过短,可能误判死锁;规定时间过长,可能不能及时发现死锁。
2. 等待图法:并发控制子系统周期性地生成事务等待图,动态地反映所有事务的等待情况。如果发现图中存在回路,则表示系统中出现了死锁。
解除方法:通常撤销一个处理代价最小的事务,释放此事务持有的所有锁,使其他事务得以继续运行下去。
可串行性是并发事务正确调度的准则。
当且仅当多个事务的并发执行结果,与按某一次序的串行执行结果相同,这种并发调度策略才是可串行化调度,即具有可串行性。
例子(《数据库系统概论(第5版)》p318):
事务T1:读B;A=B+1;写A
事务T2:读A;B=A+1;写B
那么如何判断可串行化调度呢?
我们需要引入一个概念——冲突操作,冲突操作是指不同事务对同一个数据的读写和写写操作,其他操作都属于不冲突操作。
很容易理解,对于同一数据的操作,不可能一边在读,同时另一边在写,更不可能两边都在写,这是不符合逻辑的。
在一个调度策略中,交换两个事务的不冲突操作的次序,得到另一个调度策略,如果另一个调度策略的执行结果与原来的调度相同,则称原来的调度为冲突可串行化调度。
冲突可串行化调度是可串行化调度的充分条件,但不是必要条件。
为了保证并发调度的正确性,DBMS的并发控制机制必须提供一定的手段来保证调度是可串行化的。
目前DBMS普遍采用两段锁协议(TwoPhase Locking,2PL)来实现,所有事务遵守两段锁协议是可串行化调度的充分条件,但不是必要条件。
两段锁的含义:
1)第一阶段(扩展阶段):所有事务对数据加锁,但不能解锁;
2)第二阶段(收缩阶段):所有事务对数据解锁,但不能加锁。
*需要注意的是,不同事务对同一数据的加锁仍遵循两种锁的特性以及封锁协议。
预防死锁的一次封锁法遵守两段锁协议;但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。
封锁粒度(granularity)是指封锁对象的大小。
封锁对象可以是逻辑单元,也可以是物理单元。以关系数据库为例,逻辑单元包括属性值、属性值的集合、元组、关系、索引项、索引表乃至整个数据库;物理单元包括页(数据页或索引页)、物理记录等。
封锁粒度与系统的并发度和并发控制的开销有关:封锁粒度越大,数据库能封锁的数据单元越少,并发度越小,系统开销也变小。
一般来说,处理个别元组的事务以元组为封锁粒度;处理某个关系的大量元组的事务以关系为封锁粒度;处理多个关系的大量元组的事务以数据库为封锁粒度。
在一个系统中,提供多种封锁粒度给不同的事务选择,这种封锁方法称为多粒度封锁( multiple granularity locking)。
在多粒度封锁中,显式封锁和隐式封锁的效果是一样的,因此系统检查封锁冲突时,不仅要检查显式封锁,还要沿着多粒度树上下检查隐式封锁。
显然,这样的检查方法效率很低。为此人们引进了一种新型锁,称为意向锁(intention lock)。
对任何一个结点加锁时,必须先对它的上层结点加意向锁。
三种常用的意向锁:
1)意向共享锁(IS锁):
对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁。
事务T1对数据对象A加上IS锁后,事务T2可以继续加除X锁以外的锁。
2)意向排他锁(IX锁):
对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。
事务T1对数据对象A加上IX锁后,事务T2只能继续加IS或IX锁。
3)共享意向排他锁(SIX = S+IX锁):
对一个数据对象先加S锁,再加IX锁。例如对某个表加SIX锁,则表示该事务要读(S)整个表,同时会更新(IX
)个别元组。
事务T1对数据对象A加上SIX锁后,事务T2只能加IS锁。
在具有意向锁的多粒度封锁方法中,任意事务T要对一个数据对象加锁,必须先对它的上层结点加意向锁。
申请封锁时应该按自上而下的次序进行,释放封锁时则应该按自下而上的次序进行。(栈结构)
具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销,已经在实际的DBMS产品中得到广泛使用。
锁的强度是指它对其他锁的排斥程度。
一个事务在申请封锁时,以强锁代替弱锁是安全的,反之不然。
强度排序:
X > SIX > S / IX > IS