数据库系统概论 并发控制
并发控制
概述
我们记\(R_i(x)\)为事务\(i\)对数据\(x\)进行读操作,\(W_i(x)\)为事务\(i\)对数据\(x\)进行写操作。
普通并发操作带来的问题:
- 丢失修改(
lost update
)- 两个事务对同一数据进行修改,后者事务破坏了前者事务修改的结果,使得前者事务的修改结果丢失
- 不可重复读(
non-repeatable read
)- 事务\(T_1\)读取数据后,事务\(T_2\)对该数据进行的更新操作(修改原数据,插入新数据,删除部分数据),是事务\(T_1\)无法再现前一次读的结果
- 脏读(
dirty read
)- 事务\(T_1\)修改数据并写回磁盘,事务\(T_2\)读取改数据后,事务\(T_1\)因某种原因被撤销(ROLLBACK),被\(T_1\)修改过的值恢复原值,但\(T_2\)读取的数据和数据库的数据不一致
并发控制的方式有:封锁、时间戳、乐观控制法、多版本并发控制等。
封锁
封锁的类型
- 排他锁(写锁)
- 该锁允许本事务对上锁数据进行读和写操作,不允许其他事务对上锁数据进行上锁,直至锁释放
- 共享锁(读锁)
- 该锁只允许本事务对上锁数据进行读操作,但也允许其他事务对上锁数据加共享锁,直至锁释放
封锁协议
规定了锁何时申请,持有时间,何时释放
一级封锁协议
事务T
修改数据R
之前必须加X
锁,直至事务结束(COMMIT
或ROLLBACK
)后释放X
锁。没有限定读锁,可能会造成其他事务的脏读和不可重复读。
二级封锁协议
在一级封锁协议下,要求事务T
在读取数据R
之前必须加S
锁,读完即可释放S
锁。因为一次读操作并不一定是事务全部,期间如果有其他事务对数据进行读操作,可能之后再一次读会产生数据不一致,即出现不可重复读问题。
三级封锁协议
在一级封锁协议下,要求事务T
在读取数据R
之前必须加S
锁,必须直至事务结束才能释放S
锁。保证事务期间其他读锁不被申请(因为T
事务写锁的存在),从而保证一致性。
锁的要求
X锁 | X锁 | S锁 | S锁 | |
---|---|---|---|---|
操作结束释放 | 事务结束释放 | 操作结束释放 | 事务结束释放 | |
一级 | 是 | |||
二级 | 是 | 是 | ||
三级 | 是 | 是 |
一致性保证
丢失修改 | 脏读 | 不可重复读 | |
---|---|---|---|
一级 | 不会 | 会 | 会 |
二级 | 不会 | 不会 | 会 |
三级 | 不会 | 不会 | 不会 |
活锁和死锁
死锁
死锁的预防
数据库灵活又要求性能,因此一般不会进行太多死锁的预防,普遍是对死锁的诊断和解除
- 一次封锁法:事务的执行要求必须一次性封锁所有需要使用的数据,否则不予执行
- 缺点:
- 由于数据库很多时候不确定需要锁具体哪些数据,可能依赖于一些条件,所有这样的情况下只能全锁,降低并发性
- 缺点:
- 顺序封锁法:预先对数据规定封锁数据,所有对这个数据操作的事务都需要按照这个顺序封锁
- 缺点:
- 随着数据的增改删,封锁顺序维护成本高
- 对于事务而言不够灵活
- 缺点:
死锁的诊断和解除
- 诊断
- 超时法:若事务执行超时则猜测存在死锁
- 等待图法:等待图是有向图,等待事务指向被等待事务,若出现环则出现死锁
- 解除
- 选择处理死锁代价最小的事务,将其撤销(数据恢复),为其他事务让行。
并发调度的可串行性
事务的串行执行:每个时刻只有一个事务执行。
可串行化(serializable)调度
多个事务并发执行是正确的,当且仅当其结果和某一次串行执行时的结果相同,这样的调度策略称作可串行化调度。即,只要能找到任意一种串行执行的调度和当前并发执行策略的结果一致,就说明当前的并发可以得到正确的结果,即这次并发执行正确,而使用这样的并发执行的策略是称为可串行化调度。
我们希望从一个朴素的串行执行的任务(调度)中得到一个可串行化调度,这样可以提升系统的并发性,同时保证了数据的一致性。
冲突可串行化调度
冲突操作:不同事务对同一数据的读写操作和写写操作
不可交换操作:冲突操作(数据一致性要求的限制)和同一事务的任意两个操作(事务的目的限制,肯定不能把事情颠倒着做)。
将一个调度Sc在保证不出现不可交换操作的情况下对事务间的不冲突操作进行重新调序,如果能够得到一个串行执行的调度,那么这个Sc是冲突可串行化。若一个调度是冲突可串行化的,那么一定是可串行化调度(充分条件)。
两段锁协议
由冲突可串行化调度我们知道,我们最开始获得的调度是一个个串行执行的事务,这样无利用CPU并行计算的能力,不可避免地存在性能浪费,于是我们需要基于给定的原始调度,试图寻找一个最有利于设备的可串行化调度,保证数据一致性的前提下最大程度上实现并行,于是提出了两段锁协议(2PL)来保证寻找过程的每一步结果都是一个可串行化调度。
两段锁协议
- 第一阶段获得锁
- 所有事务在进行读、写操作之前首先要申请并获得对应数据的锁,此时不能释放锁
- 第二阶段释放锁
- 释放一个锁后,事务不能再申请或获得其他锁,此时不能申请锁
通俗的说就是要锁锁一窝
若所有事务都遵守两段锁协议,则这些事务的任意调度策略都是可串行化调度(充分条件)。
封锁的粒度
以下未总结