MVCC 可重复读原理与快照版本机制
文章目录
- 《MVCC 可重复读原理与快照版本机制》
- 一、前言:为什么需要 MVCC
- 二、可重复读与快照读
- (1)快照读(Snapshot Read)
- (2)当前读(Current Read)
- 三、隐藏列:版本控制的基础
- 四、版本链(Version Chain)
- 五、ReadView(可见性判断机制)
- (1)ReadView 的定义
- (2)可见性规则(核心)
- (3)ReadView 的生成时机
- 六、MVCC 下的查询流程
- 七、可重复读如何避免幻读
- 八、面试高频问题与答题模板
- 九、总结
《MVCC 可重复读原理与快照版本机制》
一、前言:为什么需要 MVCC
大家好,我是程序员卷卷狗。
在数据库高并发环境下,如果每个事务都使用“加锁”的方式来保证隔离性,
不仅会大幅降低性能,还可能出现死锁。
于是 InnoDB 引入了 MVCC(Multi-Version Concurrency Control,多版本并发控制)。
它通过“版本链 + 快照”机制,让大多数读操作不加锁,也能保持一致性。
一句话总结:
MVCC 是一种“用时间换锁”的机制:
让读操作看到事务开始时的数据快照,而不阻塞写操作。
二、可重复读与快照读
InnoDB 默认事务隔离级别为 REPEATABLE READ(可重复读)。
(1)快照读(Snapshot Read)
读取的是某个事务开始时的数据版本,不加锁。
SELECT * FROM user WHERE id = 1;
(2)当前读(Current Read)
读取最新数据并加锁。
SELECT * FROM user WHERE id = 1 FOR UPDATE;
UPDATE user SET balance = balance - 100 WHERE id = 1;
区别:
| 类型 | 是否加锁 | 读取数据 |
|---|---|---|
| 快照读 | 否 | 事务开始时的一致性快照 |
| 当前读 | 是 | 最新提交数据 |
InnoDB 通过 MVCC 来支持“快照读”的一致性。
三、隐藏列:版本控制的基础
InnoDB 在每一行数据后都自动维护了三个隐藏字段
| 隐藏列 | 含义 |
|---|---|
DB_TRX_ID | 最近修改该行的事务 ID |
DB_ROLL_PTR | 指向 undo log 的回滚指针 |
DB_ROW_ID | 唯一行标识(无主键表使用) |
这三个隐藏列帮助 InnoDB 追踪每条记录的版本变化。
示意:
(id=1, name='卷卷狗', balance=1000, DB_TRX_ID=90, DB_ROLL_PTR=指针1)
四、版本链(Version Chain)
每次对行的修改(UPDATE / DELETE),
InnoDB 都会复制旧版本到 undo log,
并在新版本中用 DB_ROLL_PTR 指向上一个版本。
形成如下版本链
当前行:
┌────────────────────┐
│ id=1, balance=1200 │ ← DB_TRX_ID=90
│ DB_ROLL_PTR → 指针1 │
└────────────────────┘↓
历史版本1(undo log):
┌────────────────────┐
│ id=1, balance=1000 │ ← DB_TRX_ID=80
│ DB_ROLL_PTR → 指针2 │
└────────────────────┘↓
历史版本2(undo log):
┌────────────────────┐
│ id=1, balance=800 │ ← DB_TRX_ID=60
└────────────────────┘
这就是一条记录在事务演化中的“多版本链”。
读取数据时,InnoDB 会根据当前事务的 ReadView 判断哪一版本可见。
五、ReadView(可见性判断机制)
(1)ReadView 的定义
ReadView 是 InnoDB 在事务执行快照读时生成的一份“事务可见性快照”。
它包含以下关键信息:
| 字段 | 含义 |
|---|---|
m_ids | 当前活跃事务 ID 列表 |
min_trx_id | 活跃事务中最小的事务 ID |
max_trx_id | 下一个待分配事务 ID(未使用) |
creator_trx_id | 当前事务 ID |
ReadView 的作用:判断某行版本是否对当前事务可见。
(2)可见性规则(核心)
假设某行版本的事务 ID 为 trx_id,
判断可见性规则如下
- 若
trx_id < min_trx_id→ 可见(已提交); - 若
trx_id >= max_trx_id→ 不可见(未开始); - 若
trx_id在m_ids(活跃事务)中 → 不可见; - 其他情况 → 可见。
总结:
ReadView 只允许看到在自己事务开始前已提交的数据版本。
(3)ReadView 的生成时机
| 隔离级别 | ReadView 生成时机 | 效果 |
|---|---|---|
| READ COMMITTED | 每次 SELECT 都生成 | 不可重复读 |
| REPEATABLE READ | 第一次 SELECT 时生成 | 可重复读 |
这就是为什么在可重复读级别下,同一个事务内多次查询结果一致。
六、MVCC 下的查询流程
以 SELECT * FROM user WHERE id=1; 为例
- 当前事务生成 ReadView;
- 读取最新版本行(最新 DB_TRX_ID);
- 若该版本不可见,根据 DB_ROLL_PTR 找到上一版本;
- 重复判断直到找到可见版本;
- 返回该版本数据。
这就是快照读的完整逻辑 —— “沿着版本链向后回溯,直到读到自己能看的版本”。
七、可重复读如何避免幻读
InnoDB 在 可重复读(RR) 隔离级别下结合了:
- MVCC:避免脏读、不可重复读;
- 间隙锁(Gap Lock):防止幻读(insert 幻象行)。
例如:
SELECT * FROM account WHERE balance > 1000 FOR UPDATE;
→ 会锁住满足条件的行 + 条件区间的“间隙”,
防止其他事务插入新记录导致“幻读”。
MVCC + 间隙锁 = InnoDB 可重复读的完整实现。
八、面试高频问题与答题模板
| 问题 | 答案要点 |
|---|---|
| Q1:什么是 MVCC? | 多版本并发控制,让读操作不加锁即可获得一致性视图。 |
| Q2:MVCC 如何实现? | 通过 undo log 保存旧版本,ReadView 判断可见版本。 |
| Q3:ReadView 有哪些字段? | m_ids, min_trx_id, max_trx_id, creator_trx_id。 |
| Q4:可重复读如何避免不可重复读? | 同一事务使用相同 ReadView。 |
| Q5:MVCC 能避免幻读吗? | 部分可避免,InnoDB 还需配合间隙锁。 |
| Q6:READ COMMITTED 与 REPEATABLE READ 的区别? | 前者每次查询生成 ReadView,后者只生成一次。 |
| Q7:undo log 在 MVCC 中的作用? | 存放旧版本数据供快照读访问。 |
九、总结
MVCC 的核心思想是:
通过版本链与 ReadView,让读写操作彼此独立而不加锁。
InnoDB 的实现依赖于:
- undo log(保存历史版本);
- 隐藏列(版本元信息);
- ReadView(事务可见性);
- 间隙锁(防幻读)。
一句话记住:
MVCC 让读操作像“读历史”,写操作像“写当下”。
下一篇(第 12 篇),我将写——
《MySQL 四种隔离级别:从脏读到幻读的全过程》,
以事务交叉时间线的形式,展示四种隔离级别的可见性与并发控制。
