> For the complete documentation index, see [llms.txt](https://wtifs.gitbook.io/diva-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wtifs.gitbook.io/diva-notes/db/my-sql/ge-li-ji-bie.md).

# 隔离级别

## 隔离性引发的并发问题

#### 脏读

`B` 事务读取到了 `A` 事务尚未提交的数据

#### 不可重复读

指 `A` 事务在事务期间两次查询，读取到的**数据内容**不一致（因为期间别的线程修改了数据）

* 解决：`RR` 隔离级别下的 `MVCC`

#### 幻读

指 `A` 事务在 `B` 事务提交之前和提交之后两次查询，读取到的**数据行数**不一致（同一条数据）

* 线程 `T1` 更新了 `id <= 2` 数据，`T2` 插了条新的 `id=2` 的数据，`T1` 再读数据，会发现有一条没有被更新
* 解决：`LBCC` 和 `MVCC` 的解决方案

**MVCC**

Multi-Version Concurrent Control，多版本并发控制

快照读的解决方式。读的是历史版本的数据。

`MVCC` 的解决方式是，通过 `readView` 记录第一次读时的事务版本，第二次读的时候，只查找事务ID <= 第一次版本的数据

`MVCC` 性能比 `LBCC` 好。但仍可能产生问题，这种情况下需要通过 `LBCC` 手动加锁。如：

| T1        | T2   |
| --------- | ---- |
| 查询全表行数，为1 |      |
|           | 写入新行 |
| UPDATE 行  |      |
| 查询全表行数，为2 |      |

上述例子由于做了 `UDPATE` 操作，之前的快照失效了，这种情况下的快照策略并没能真正避免幻读。只能通过在第一次读时锁定区间来解决。

**LBCC**

`Lock-Based Concurrent Control`，基于锁的并发控制。

当前读 / 锁定读的解决方式。读的是当前数据的最新版本，读到后会加锁，防止别的事务修改。

`LBCC` 的解决方案是，通过 `SELECT FOR UPDATE` 的语法在 `T1` 里使用间隙锁，锁定区间的读，使 `T2` 阻塞，强行使 `T1` 完成后才执行 `T2`，就不会有上述麻烦的问题

间隙锁只存在于 `RR` 级别下，因为它主要用于防止幻读，其他级别下不需要。

## 隔离级别

1. Read uncommitted (读未提交)：最低级别，任何情况都无法保证。
2. Read committed (读已提交)：可避免脏读的发生。
3. Repeatable read (可重复读)：可避免脏读、不可重复读的发生。`MySQL` 默认隔离级别。
4. Serializable (串行化)：可避免脏读、不可重复读、幻读的发生。

#### 未提交读 READ UNCOMMITTED

在该级别事务中的修改即使没有被提交，对其他事务也是可见的。事务可以读取其他事务修改完但未提交的数据，这种问题称为脏读。性能没有比其他级别好很多，很少使用。

#### 提交读 READ COMMITTED

多数数据库系统默认的隔离级别。提交读满足了隔离性的简单定义：一个事务开始时只能"看见"已经提交的事务所做的修改。换句话说，一个事务从开始直到提交之前的任何修改对其他事务都是不可见的。也叫不可重复读，因为两次执行同样的查询可能会得到不同结果。

#### 可重复读 REPEATABLE READ（MySQL默认的隔离级别）

可重复读解决了不可重复读的问题，保证了在同一个事务中多次读取同样的记录结果一致。但还是无法解决幻读，所谓幻读指的是当某个事务在读取某个范围内的记录时，两次读取的数据量不一致。`InnoDB` 存储引擎通过多版本并发控制 `MVCC` 解决多数幻读的问题，但无法完全解决。

#### 可串行化 SERIALIZABLE

最高的隔离级别，通过强制事务串行执行，避免幻读。可串行化会在读取的每一行数据上都加锁，可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别，只有非常需要确保数据一致性且可以接受没有并发的情况下才考虑该级别。
