> For the complete documentation index, see [llms.txt](https://lewiszlw.gitbook.io/notebooks/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lewiszlw.gitbook.io/notebooks/blogs/history/bing-fa-kong-zhi.md).

# 并发控制

## 并发控制概述

并发控制机制的任务

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

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

* 丢失修改（Lost Update）
* 不可重复读（Non-repeatable Read）
  * 不可重复读是指事务T1读取数据后，事务T2执行更新操作，使T1无法再现前一次读取结果。
  * 不可重复读包括三种情况：
    * 事务T1读取某一数据后，事务T2对其做了修改，当事务T1再次读该数据时，得到与前一次不同的值
    * 事务T1按一定条件从数据库中读取了某些数据记录后，事务T2删除了其中部分记录，当T1再次按相同条件读取数据时，发现某些记录消失了
    * 事务T1按一定条件从数据库中读取某些数据记录后，事务T2插入了一些记录，当T1再次按相同条件读取数据时，发现多了一些记录
* 读“脏”数据（Dirty Read） 读“脏”数据是指：事务T1修改某一数据，并将其写回磁盘，事务T2读取同一数据后，T1由于某种原因被撤销，这时T1已修改过的数据恢复原值，T2读到的数据就与数据库中的数据不一致，T2读到的数据就为“脏”数据，即不正确的数据。

数据不一致性的原因：由于并发操作破坏了事务的隔离性。 并发控制就是要用正确的方式调度并发操作，使一个用户事务的执行不受其他事务的干扰，从而避免造成数据的不一致性。

并发控制的主要技术

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

## 封锁

什么是封锁

* 封锁就是事务T在对某个数据对象（例如表、记录等）操作之前，先向系统发出请求，对其加锁
* 加锁后事务T就对该数据对象有了一定的控制，在事务T释放它的锁之前，其它的事务不能更新此数据对象

基本封锁类型

* 排它锁（eXclusive Locks，简记为X锁）
  * 排它锁又称为写锁
  * 若事务T对数据对象A加上X锁，则只允许T读取和修改A，其它任何事务都不能再对A加任何类型的锁，直到T释放A上的锁
  * 保证其他事务在T释放A上的锁之前不能再读取和修改A
* 共享锁（Share Locks，简记为S锁）
  * 共享锁又称为读锁
  * 若事务T对数据对象A加上S锁，则其它事务只能再对A加S锁，而不能加X锁，直到T释放A上的S锁
  * 保证其他事务可以读A，但在T释放A上的S锁之前不能对A做任何修改

## 封锁协议

在运用X锁和S锁对数据对象加锁时，需要约定一些规则：封锁协议（Locking Protocol）

* 何时申请X锁或S锁
* 持锁时间、何时释放

常用的封锁协议：

1. 一级封锁协议

* 事务T在修改数据R之前必须先对其加X锁，直到事务结束才释放
* 一级封锁协议可防止丢失修改
* 在一级封锁协议中，如果是读数据，不需要加锁的，所以它不能保证可重复读和不读“脏”数据。

1. 二级封锁协议

* 一级封锁协议+事务T在读取数据R前必须先加S锁，读完后即可释放S锁
* 二级封锁协议可以防止丢失修改和读“脏”数据
* 在二级封锁协议中，由于读完数据后即可释放S锁，所以它不能保证可重复读

1. 三级封锁协议

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

|              | 操作结束释放（X锁） | 事务结束释放（X锁） | 操作结束释放（S锁） | 事务结束释放（S锁） | 不丢失数据 | 不读“脏”数据 | 可重复读 |
| ------------ | ---------- | ---------- | ---------- | ---------- | ----- | ------- | ---- |
| 一级封锁协议       |            | √          |            |            | √     |         |      |
| 二级封锁协议       |            | √          | √          |            | √     | √       |      |
| 三级封锁协议       |            | √          |            | √          | √     | √       | √    |
| # 活锁和死锁      |            |            |            |            |       |         |      |
| 封锁技术带来的一些新问题 |            |            |            |            |       |         |      |

* 活锁
* 死锁

### 活锁

活锁示例：

* 事务T1封锁了数据R
* 事务T2又请求封锁R，于是T2等待。
* T3也请求封锁R，当T1释放了R上的封锁之后系统首先批准了T3的请求，T2仍然等待。
* T4又请求封锁R，当T3释放了R上的封锁之后系统又批准了T4的请求……
* T2有可能永远等待，这就是活锁的情形

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

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

### 死锁

死锁示例：

* 事务T1封锁了数据R1
* T2封锁了数据R2
* T1又请求封锁R2，因T2已封锁了R2，于是T1等待T2释放R2上的锁
* 接着T2又申请封锁R1，因T1已封锁了R1，T2也只能等待T1释放R1上的锁
* 这样T1在等待T2，而T2又在等待T1，T1和T2两个事务永远不能结束，形成死锁

解决死锁的方法

1. 预防死锁

* 产生死锁的原因是两个或多个事务都已封锁了一些数据对象，然后又都请求对已为其他事务封锁的数据对象加锁，从而出现死等待。
* 预防死锁的发生就是要破坏产生死锁的条件
* 预防死锁的方法
  * 一次封锁法 要求每个事务必须一次将所有要使用的数据全部加锁，否则就不能继续执行
  * 顺序封锁法 顺序封锁法是预先对数据对象规定一个封锁顺序，所有事务都按这个顺序实行封锁。

1. 死锁的诊断与解除

* 死锁的诊断
  * 超时法 如果一个事务的等待时间超过了规定的时限，就认为发生了死锁
  * 事务等待图法 用事务等待图动态反映所有事务的等待情况
* 解除死锁
  * 选择一个处理死锁代价最小的事务，将其撤消
  * 释放此事务持有的所有的锁，使其它事务能继续运行下去

## 并发调度的可串行性

### 可串行化调度

可串行化(Serializable)调度：多个事务的并发执行是正确的，当且仅当其结果与按某一次序串行地执行这些事务时的结果相同。

可串行性(Serializability)

* 是并发事务正确调度的准则
* 一个给定的并发调度，当且仅当它是可串行化的，才认为是正确调度

### 冲突可串行化调度

冲突操作：是指不同的事务对同一数据的读写操作和写写操作。

一个调度Sc在保证冲突操作的次序不变的情况下，通过交换两个事务不冲突操作的次序得到另一个调度Sc’，如果Sc’是串行的，称调度Sc是冲突可串行化的调度。

若一个调度是冲突可串行化，则一定是可串行化的调度。

## 两段锁协议

两段封锁协议(Two-Phase Locking，简称2PL)是最常用的一种封锁协议，理论上证明使用两段封锁协议产生的是可串行化调度。

两段锁协议：指所有事务必须分两个阶段对数据项加锁和解锁

* 在对任何数据进行读、写操作之前，事务首先要获得对该数据的封锁
* 在释放一个封锁之后，事务不再申请和获得任何其他封锁

“两段”锁的含义：事务分为两个阶段

* 第一阶段是获得封锁，也称为扩展阶段 事务可以申请获得任何数据项上的任何类型的锁，但是不能释放任何锁
* 第二阶段是释放封锁，也称为收缩阶段 事务可以释放任何数据项上的任何类型的锁，但是不能再申请任何锁

两段锁协议与防止死锁的一次封锁法

* 一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁，否则就不能继续执行，因此一次封锁法遵守两段锁协议
* 但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁，因此遵守两段锁协议的事务可能发生死锁

## 封锁的粒度

封锁对象的大小称为封锁粒度（Granularity）。

封锁的对象：逻辑单元，物理单元

* 逻辑单元: 属性值、属性值集合、元组、关系、索引项、整个索引、整个数据库等
* 物理单元：页（数据页或索引页）、物理记录等

封锁粒度与系统的并发度和并发控制的开销密切相关。

* 封锁的粒度越大，数据库所能够封锁的数据单元就越少，并发度就越小，系统开销也越小；
* 封锁的粒度越小，并发度较高，但系统开销也就越大

### 多粒度封锁

多粒度树

* 以树形结构来表示多级封锁粒度
* 根结点是整个数据库，表示最大的数据粒度
* 叶结点表示最小的数据粒度

多粒度封锁协议

* 允许多粒度树中的每个结点被独立地加锁
* 对一个结点加锁意味着这个结点的所有后裔结点也被加以同样类型的锁
* 在多粒度封锁中一个数据对象可能以两种方式封锁：显式封锁和隐式封锁
  * 显式封锁: 直接加到数据对象上的封锁
  * 隐式封锁:是该数据对象没有独立加锁，是由于其上级结点加锁而使该数据对象加上了锁
  * 显式封锁和隐式封锁的效果是一样的

### 意向锁

意向锁的含义是如果对一个结点加意向锁，则说明该结点的下层结点正在被加锁；对任一结点加基本锁，必须先对它的上层结点加意向锁。

常用意向锁

* 意向共享锁(Intent Share Lock，简称IS锁) 如果对一个数据对象加IS锁，表示它的后裔结点拟（意向）加S锁
* 意向排它锁(Intent Exclusive Lock，简称IX锁) 如果对一个数据对象加IX锁，表示它的后裔结点拟（意向）加X锁
* 共享意向排它锁(Share Intent Exclusive Lock，简称SIX锁) 如果对一个数据对象加SIX锁，表示对它加S锁，再加IX锁，即SIX = S + IX

***

> 《数据库系统概论（第5版）》王珊 萨师煊 著
