MVCC
...大约 3 分钟MySQL数据库
MVCC 指的是多版本并发控制,是一种并发控制机制。简单来说,就是给我们的 MySQL 数据拍个“快照”,定格某个时刻数据库的状态。允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。
在 MySQL 中,MVCC 是通过隐藏字段,undo log版本链和 ReadView 机制来实现的。
多版本:指mysql维护着行数据的多个版本
并发控制:在多个事务同时操作某一行记录时mysql控制返回多个版本的行记录中的某个版本。

1. 隐藏字段:每条记录的小秘密
- DB_TRX_ID:记录最后一次修改这条数据的事务 ID。
- 比如事务 A 修改了这条数据,那么这条数据的
DB_TRX_ID
就是事务 A 的 ID。
- 比如事务 A 修改了这条数据,那么这条数据的
- DB_ROLL_PTR:指向这条数据的旧版本(历史版本)的指针。
- 比如事务 A 修改了数据,旧版本的数据会被存到 Undo Log 里,
DB_ROLL_PTR
就是指向这个旧版本的“地址”。
- 比如事务 A 修改了数据,旧版本的数据会被存到 Undo Log 里,
2. Undo Log 版本链:数据的时光机
- 每次修改数据时,MySQL 不会直接覆盖旧数据,而是把旧数据存到一个叫 Undo Log 的地方。
- 这些旧数据通过
DB_ROLL_PTR
连成一条链,就像一个时光机,可以找到这条数据的所有历史版本。 - 比如:
- 事务 A 把
balance
从 1000 改成 900,旧版本 1000 被存到 Undo Log。 - 事务 B 又把
balance
从 900 改成 800,旧版本 900 也被存到 Undo Log。 - 这时候,
DB_ROLL_PTR
就指向了一个版本链:800 → 900 →1000。
- 事务 A 把
3. ReadView:决定你能看到什么
- 当一个事务想读数据时,MySQL 会为它创建一个 ReadView。
- ReadView 就像是一个“快照”,决定了这个事务能看到哪些数据版本。
- ReadView 包含以下信息:
- 活跃事务列表:当前正在运行的事务 ID。
- 最小事务 ID:当前系统中最老的事务 ID。
- 最大事务 ID:当前系统中最新的事务 ID(:事务 ID 的最大值加一。换句话说,它是下一个将要生成的事务 ID)。
- 创建者事务 ID:创建这个 ReadView 的事务 ID。

用一个例子来说明整个过程:
场景
- 表
users
中有一条数据:id=1, name='Alice', balance=1000
。 - 事务 A(ID=100)把
balance
从 1000 改成 900。 - 事务 B(ID=101)想读取
balance
的值。
步骤 1:事务 A 修改数据
- 事务 A 把
balance
从 1000 改成 900。 - MySQL 做了两件事:
- 把旧数据
balance=1000
存到 Undo Log。 - 更新当前数据的
DB_TRX_ID=100
(事务 A 的 ID),DB_ROLL_PTR
指向 Undo Log 中的旧版本(1000)。
- 把旧数据
步骤 2:事务 B 读取数据
- 事务 B 想读取
balance
的值。 - MySQL 为事务 B 创建一个 ReadView:
- 活跃事务列表:
[100]
(事务 A 还在运行)。 - 最小事务 ID:100。
- 最大事务 ID:101。
- 创建者事务 ID:101(事务 B 的 ID)。
- 活跃事务列表:
- 事务 B 开始读取数据:
- 检查当前数据的
DB_TRX_ID=100
。 - 判断
DB_TRX_ID
是否可见:- 如果
DB_TRX_ID
小于最小事务 ID(100 < 100?不成立),说明数据是事务 B 开始之前提交的,可见。 - 如果
DB_TRX_ID
在活跃事务列表中(100 在[100]
中),说明数据是未提交的,不可见。 - 如果
DB_TRX_ID
大于等于最大事务 ID(100 >= 101?不成立),说明数据是事务 B 开始之后创建的,不可见。
- 如果
- 发现
DB_TRX_ID=100
在活跃事务列表中,说明数据是未提交的,不可见。 - 通过
DB_ROLL_PTR
找到旧版本的数据balance=1000
,返回给事务 B。
- 检查当前数据的
步骤 3:事务 A 提交
- 事务 A 提交后,数据的
DB_TRX_ID=100
变成已提交状态。 - 如果事务 B 再次读取数据:
- 检查
DB_TRX_ID=100
,发现它不在活跃事务列表中,说明数据已提交,可见。 - 直接返回当前数据
balance=900
。
- 检查