MVCC 全称是 Multi-Version Concurrency Control,也就是多版本并发控制。它的核心思想是:为同一行数据维护多个版本,让读写在很多情况下不用互相阻塞。
没有 MVCC 时,读写冲突通常要大量依赖锁。MVCC 让普通 select 可以读一个可见的数据版本,而不是必须等待正在修改这行数据的事务结束。

MVCC 依赖哪三样东西
InnoDB 的 MVCC 主要依赖:
- 记录中的隐藏字段。
- undo log 版本链。
- ReadView 读视图。
这三者配合,解决一个问题:当前事务应该看到这行数据的哪个版本?
记录中的隐藏字段
InnoDB 每行记录除了业务字段,还会维护一些隐藏字段。
| 隐藏字段 | 含义 |
|---|---|
DB_TRX_ID |
最近一次修改这行记录的事务 ID |
DB_ROLL_PTR |
回滚指针,指向 undo log 中的上一个版本 |
DB_ROW_ID |
隐藏行 ID,没有主键时可能使用 |
其中 DB_TRX_ID 和 DB_ROLL_PTR 是理解 MVCC 的关键。
undo log 版本链
当一行数据被多个事务修改时,undo log 会形成一条版本链。链表头部通常靠近较新的旧版本,沿着 roll_pointer 可以找到更早的版本。

例如一条记录从 A1 被改成 A2,又被改成 A3。当前数据页里是最新值,而 undo log 中保存旧值。不同事务根据可见性规则,可能读到 A3、A2 或 A1。
当前读和快照读
不是所有读都走 MVCC 快照。
| 类型 | SQL 示例 | 特点 |
|---|---|---|
| 当前读 | select ... for update、update、delete、insert |
读取最新版本,并加锁 |
| 快照读 | 普通 select |
读取可见版本,不加锁,非阻塞 |
MVCC 主要服务于快照读。普通 select 不加锁也能在并发写入时稳定读取,就是因为它读的是某个可见版本。
ReadView 是什么
ReadView 是快照读执行时生成的读视图,用来判断版本链上的某个版本是否对当前事务可见。
ReadView 中有四个核心字段:
| 字段 | 含义 |
|---|---|
creator_trx_id |
创建这个 ReadView 的事务 ID |
m_ids |
创建 ReadView 时活跃且未提交的事务 ID 集合 |
min_trx_id |
活跃事务中的最小 ID |
max_trx_id |
下一个将要分配的事务 ID |
版本可见性规则
当沿着版本链找到某个版本时,会拿这个版本的 trx_id 和 ReadView 比较。

这块最适合按流程走。每遇到一个版本,就问几个问题:
是
否
是
否
是
否
是
否
快照读开始
生成或复用 ReadView
读取当前记录版本
trx_id 是否等于 creator_trx_id
版本可见
trx_id 是否小于 min_trx_id
trx_id 是否大于等于 max_trx_id
版本不可见
trx_id 是否在 m_ids 中
沿 undo log 版本链找上一个版本
规则可以简化为:
trx_id == creator_trx_id:自己改的,能看见。trx_id < min_trx_id:这个版本在 ReadView 生成前已经提交,能看见。trx_id >= max_trx_id:这个版本在 ReadView 生成后才出现,不能看见。min_trx_id <= trx_id < max_trx_id:如果trx_id不在m_ids中,说明已提交,能看见;如果在m_ids中,说明当时还活跃,不能看见。
这些规则的目的很朴素:只让当前事务看到它应该看到的已提交版本。
RC 和 RR 的区别
READ COMMITTED 和 REPEATABLE READ 的核心差异之一,是生成 ReadView 的时机不同。
| 隔离级别 | ReadView 生成时机 | 结果 |
|---|---|---|
| READ COMMITTED | 每次执行快照读都生成新的 ReadView | 同一事务内多次查询可能看到新提交数据 |
| REPEATABLE READ | 第一次快照读生成 ReadView,后续复用 | 同一事务内多次查询结果更稳定 |

两种隔离级别的差别,可以这样看:
RC
RR
同一个事务内多次普通 select
隔离级别
每次 select 生成新的 ReadView
可能看到其他事务新提交的数据
第一次 select 生成 ReadView
后续 select 复用同一个 ReadView
同一事务内读取结果更稳定
这也解释了为什么 RC 下可能出现不可重复读,而 RR 能让普通快照读保持可重复。
一个简单例子
假设事务 5 创建了 ReadView:
text
m_ids = {3, 4, 5}
min_trx_id = 3
max_trx_id = 6
creator_trx_id = 5
现在沿着版本链看到几个版本:
| 版本 trx_id | 是否可见 | 原因 |
|---|---|---|
| 5 | 可见 | 当前事务自己修改 |
| 2 | 可见 | 小于 min_trx_id,早已提交 |
| 6 | 不可见 | 不小于 max_trx_id,属于更晚事务 |
| 4 | 不可见 | 在 m_ids 中,当时未提交 |
如果某个版本不可见,就继续沿着 undo log 版本链向前找,直到找到可见版本或没有更旧版本。
面试回答模板
可以这样回答:
MVCC 是多版本并发控制,用来降低读写冲突。InnoDB 每行记录有隐藏字段,比如事务 ID 和回滚指针;更新数据时会生成 undo log,多个旧版本通过回滚指针形成版本链。普通 select 是快照读,会基于 ReadView 判断版本是否可见。ReadView 中包含当前活跃事务集合、最小活跃事务 ID、下一个事务 ID 和创建者事务 ID。RC 隔离级别下每次快照读都会生成新的 ReadView,RR 隔离级别下第一次快照读生成后会复用,所以 RR 下多次读取更稳定。
小结
MVCC 的难点不是名词多,而是要把它们串成一条线:隐藏字段记录版本信息,undo log 串起旧版本,ReadView 决定哪个版本可见。理解这条线,MVCC 就从"背概念"变成了"顺着链表找可见数据"。