一沈秋园,满庭霜落,云烟北桥夜连城

MVCC 是多版本并发控制的缩写,是一种数据库和编程语言中常用的并发控制方法。它通过保存数据的历史版本,实现对数据库的高效访问。

MySQL 中 MVCC 主要是通过行记录中的隐藏字段(隐藏主键 row_id,事务ID trx_id,回滚指针 roll_pointer),undo_log(版本链),ReadView(一致性试图)来实现的。

下面是一个简单的例子:

假设有一个表 t,有两个字段 id 和 name,初始状态如下:

id

name

1

A

2

B

 

 

 

现在有两个事务 T1 和 T2,T1 在可重复读(Repeatable Read)隔离级别下,T2 在读已提交(Read Committed)隔离级别下。

T1 和 T2 的执行过程如下:

1,T1 开始,执行 select * from t; 得到结果:

id

name

1

A

2

B

 

 

 

2,T2 开始,执行 update t set name = 'C' where id = 1;  此时表 t 的行记录发生变化,变为:

id

name

row_id

trx_id

roll_pointer

1

C

100

200

300

2

B

101

NULL

NULL

 

 

 

此时 undo log 中记录了 id 为 1 的行记录的旧版本:

id

name

row_id

trx_id

roll_pointer

1

A

300

NULL

NULL

 

 

3,T1 再次执行 select * from t; 此时由于 T1 是可重复读隔离级别,它会根据自己的 ReadView 来判断那些记录是可见的。

ReadView中保存了当前活跃事务的 ID 列表,以及当前系统最小和最大事务ID。

假设 T1 的 ReadView 如下:

min_trx_id

max_trx_id

active_trx_id

100

200

[200]

 

 

那么T1会根据以下规则来判断行记录是否可见:

  • 如果行记录的trx_id为NULL,或者小于min_trx_id,说明该行记录是在T1之前就已经提交的,对T1可见。
  • 如果行记录的trx_id等于T1自己的trx_id,说明该行记录是T1自己修改的,对T1可见。
  • 如果行记录的trx_id大于max_trx_id,说明该行记录是在T1之后才修改的,对T1不可见。
  • 如果行记录的trx_id在min_trx_id和max_trx_id之间,并且不在active_trx_ids中,说明该行记录是在T1之前就已经提交的,对T1可见。
  • 如果行记录的trx_id在min_trx_id和max_trx_id之间,并且在active_trx_ids中,说明该行记录是由其他未提交事务修改的,对T1不可见。

根据这些规则,T1会发现 id 为1的行记录不可见,因为它的 trx_id 为 200,在 active_trx_ids 中。所以 T1 会沿着回滚指针找到 undo log 中的旧版本,并判断它是否可见。

旧版本的 trx_id 为NULL,小于 min_trx_id,所以对 T1 可见。因此,T1 得到结果:

id

name

1

A

2

B

 

 

 

这就是MVCC的原理,通过保存数据的历史版本,实现对数据库的高效访问,保证事务的一致性和隔离性。

接下来看T2的执行过程:

4,T2 执行 commit; 提交事务,此时表 t 的行记录变为:

id

name

row_id

trx_id

roll_pointer

1

C

100

NULL

NULL

2

B

101

NULL

NULL

 

 

 

undo log中的旧版本被删除。

5,T2再次执行 select * from t; ,此时由于 T2 是读已提交隔离级别,它会根据自己的 ReadView 来判断哪些行记录是可见的。

ReadView 中保存了当前活跃事务的 ID列表,以及当前系统最小和最大事务ID。

假设 T2 的 ReadView 如下:

min_trx_id

max_trx_id

active_trx_id

201

201

[]

 

 

那么T2会根据以下规则来判断行记录是否可见:

  • 如果行记录的 trx_id 为 NULL,或者小于 min_trx_id,说明该行记录是在 T2 之前就已经提交的,对 T2 可见。
  • 如果行记录的 trx_id 等于 T2 自己的trx_id,说明该行记录是 T2 自己修改的,对 T2 可见。
  • 如果行记录的 trx_id 大于 max_trx_id,说明该行记录是在 T2 之后才修改的,对 T2 不可见。
  • 如果行记录的 trx_id 在 min_trx_id 和 max_trx_id 之间,并且不在 active_trx_ids 中,说明该行记录是在 T2 之前就已经提交的,对 T2 可见。
  • 如果行记录的 trx_id 在 min_trx_id 和 max_trx_id 之间,并且在 active_trx_ids 中,说明该行记录是由其他未提交事务修改的,对 T2 不可见。

根据这些规则,T2 会发现 id 为 1 和 id 为 2 的行记录都可见,因为它们的 trx_id 都为 NULL,小于 min_trx_id。因此,T2 得到结果:

id

name

1

C

2

B