Skip to content

Commit d96ba4e

Browse files
committed
Update InnoDB对MVCC的实现.md
1 parent 452fc49 commit d96ba4e

File tree

1 file changed

+19
-19
lines changed

1 file changed

+19
-19
lines changed

docs/database/mysql/InnoDB对MVCC的实现.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,43 @@
1818

1919
## 一致性非锁定读和锁定读
2020

21-
#### 一致性非锁定读
21+
### 一致性非锁定读
2222

2323
对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
2424

2525
`InnoDB` 存储引擎中,[多版本控制 (multi versioning)](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html) 就是对非锁定读的实现。如果读取的行正在执行 `DELETE``UPDATE` 操作,这时读取操作不会去等待行上锁的释放。相反地,`InnoDB` 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
2626

2727
`Repeatable Read``Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,`select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read``MVCC` 实现了可重复读和防止部分幻读
2828

29-
#### 锁定读
29+
### 锁定读
3030

3131
如果执行的是下列语句,就是 [**锁定读(Locking Reads)**](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html)
3232

33-
- select ... lock in share mode
34-
- select ... for update
35-
- insertupdatedelete 操作
33+
- `select ... lock in share mode`
34+
- `select ... for update`
35+
- `insert``update``delete` 操作
3636

3737
在锁定读下,读取的是数据的最新版本,这种读也被称为 `当前读(current read)`。锁定读会对读取到的记录加锁:
3838

3939
- `select ... lock in share mode`:对记录加 `S` 锁,其它事务也可以加`S`锁,如果加 `x` 锁则会被阻塞
4040

4141
- `select ... for update``insert``update``delete`:对记录加 `X` 锁,且其它事务不能加任何锁
4242

43-
在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了在 `Repeatable Read``MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成),但如果是`当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。**所以 `InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
43+
在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了,在 `Repeatable Read``MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是!如果是 `当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。所以, **`InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
4444

4545
## InnoDB 对 MVCC 的实现
4646

4747
`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID``Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
4848

49-
#### 隐藏字段
49+
### 隐藏字段
5050

5151
在内部,`InnoDB` 存储引擎为每行数据添加了三个 [隐藏字段](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html)
5252

5353
- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务 id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除
5454
- `DB_ROLL_PTR(7字节)` 回滚指针,指向该行的 `undo log` 。如果该行未被更新,则为空
5555
- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该 id 来生成聚簇索引
5656

57-
#### ReadView
57+
### ReadView
5858

5959
[`Read View`](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L298) 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
6060

@@ -65,7 +65,7 @@
6565
- `m_ids``Read View` 创建时其他未提交的活跃事务 ID 列表。创建 `Read View`时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中)
6666
- `m_creator_trx_id`:创建该 `Read View` 的事务 ID
6767

68-
#### undo-log
68+
### undo-log
6969

7070
`undo log` 主要有两个作用:
7171

@@ -78,21 +78,21 @@
7878

7979
**`insert` 时的数据初始状态:**
8080

81-
![markdown](https://ddmcc-1255635056.file.myqcloud.com/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png)
81+
![](https://ddmcc-1255635056.file.myqcloud.com/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png)
8282

8383
2. **`update undo log`**`update``delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
8484

8585
**数据第一次被修改时:**
8686

87-
![markdown](https://ddmcc-1255635056.file.myqcloud.com/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png)
87+
![](https://ddmcc-1255635056.file.myqcloud.com/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png)
8888

8989
**数据第二次被修改时:**
9090

91-
![markdown](https://ddmcc-1255635056.file.myqcloud.com/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png)
91+
![](https://ddmcc-1255635056.file.myqcloud.com/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png)
9292

9393
不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录
9494

95-
#### 数据可见性算法
95+
### 数据可见性算法
9696

9797
`InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID``Read View` 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件
9898

@@ -127,13 +127,13 @@
127127

128128
举个例子:
129129

130-
![markdown](https://ddmcc-1255635056.file.myqcloud.com/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png)
130+
![](https://ddmcc-1255635056.file.myqcloud.com/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png)
131131

132-
#### **在 RC 下 ReadView 生成情况**
132+
### 在 RC 下 ReadView 生成情况
133133

134134
1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`**
135135

136-
![markdown](https://ddmcc-1255635056.file.myqcloud.com/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png)
136+
![](https://ddmcc-1255635056.file.myqcloud.com/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png)
137137

138138
由于 RC 级别下每次查询都会生成`Read View` ,并且事务 101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]**`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
139139

@@ -159,7 +159,7 @@
159159

160160
> **总结:** **在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读**
161161
162-
#### **在 RR 下 ReadView 生成情况**
162+
### 在 RR 下 ReadView 生成情况
163163

164164
**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)**
165165

@@ -199,11 +199,11 @@
199199

200200
`InnoDB`存储引擎在 RR 级别下通过 `MVCC``Next-key Lock` 来解决幻读问题:
201201

202-
1. **执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据**
202+
**1、执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据**
203203

204204
在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 `Read View` ,并使用至事务提交。所以在生成 `Read View` 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”
205205

206-
2. **执行 select...for update/lock in share mode、insert、update、delete 等当前读**
206+
**2、执行 select...for update/lock in share mode、insert、update、delete 等当前读**
207207

208208
在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!`InnoDB` 使用 [Next-key Lock](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-next-key-locks) 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读
209209

0 commit comments

Comments
 (0)