MVCC实现原理#
一句话答案#
MVCC 通过 undo log 版本链 + Read View 实现快照读,每行有隐藏的 trx_id 和 roll_pointer,无需加锁即可读一致性快照。
核心要点
MVCC(Multi-Version Concurrency Control,多版本并发控制):
核心思想:不加锁,通过保存数据的多个历史版本,让读操作读历史快照,写操作创建新版本,读写不互相阻塞。
MVCC 的三个核心组件:
1. 隐藏列(每行数据都有)
每行数据隐含三个字段:
DB_TRX_ID(6字节):最近修改本行的事务 ID
DB_ROLL_PTR(7字节):回滚指针,指向 undo log 中的上一个版本
DB_ROW_ID(6字节,可选):隐藏主键(无主键时使用)plaintext2. undo log(版本链)
每次修改一行,都会在 undo log 中记录旧版本,通过 DB_ROLL_PTR 串成版本链:
当前版本(事务100)→ 上一版本(事务80)→ 再上一版本(事务60)→ ...plaintext3. Read View(读视图)
Read View 在快照读时创建,包含:
creator_trx_id:创建 Read View 的当前事务 ID
trx_ids:创建时所有活跃(未提交)事务的 ID 集合
min_trx_id:trx_ids 中最小的事务 ID
max_trx_id:下一个将被分配的事务 ID(即当前最大事务ID + 1)plaintext可见性判断规则(判断某版本对当前事务是否可见):
对于版本链中某个版本的 DB_TRX_ID(设为 trx_id):
1. trx_id == creator_trx_id → 自己修改的,可见
2. trx_id < min_trx_id → 该事务在 Read View 创建前已提交,可见
3. trx_id >= max_trx_id → 该事务在 Read View 创建后才开始,不可见
4. min_trx_id <= trx_id < max_trx_id:
└─ trx_id 在 trx_ids 中 → 该事务创建时还未提交,不可见
└─ trx_id 不在 trx_ids 中 → 该事务已提交,可见
如果当前版本不可见 → 沿 DB_ROLL_PTR 找上一个版本,重复判断plaintextREAD COMMITTED vs REPEATABLE READ 的区别(Read View 生成时机):
| 隔离级别 | Read View 生成时机 |
|---|---|
| READ COMMITTED | 每次快照读都生成新的 Read View → 能看到其他事务已提交的新数据 → 不可重复读 |
| REPEATABLE READ | 整个事务只在第一次快照读时生成一次 Read View → 后续读都用同一个 → 可重复读 |
如何解决脏读:
- undo log 版本链中,未提交事务的修改,其 trx_id 在当前 Read View 的 trx_ids 中
- 根据可见性规则,未提交版本不可见 → 读到的是更早的已提交版本 → 脏读被解决
面试回答(2分钟版)
MVCC 全称多版本并发控制,核心目的是让读写互不阻塞。InnoDB 每行数据有两个隐藏字段:trx_id 记录最近修改该行的事务 ID,roll_pointer 指向 undo log 中的上一个版本,多个历史版本通过 roll_pointer 串成版本链。事务执行快照读时会创建 Read View,记录当前所有活跃事务 ID 集合、最小活跃事务 ID 和下一个待分配事务 ID。判断可见性时从版本链最新版本开始比较 trx_id:小于最小活跃 ID 说明已提交可见,大于等于待分配 ID 说明是之后开始的事务不可见,在活跃集合中说明未提交也不可见,不可见就沿 roll_pointer 继续往前找。RC 和 RR 的关键区别在于 Read View 的生成时机:RC 每次 SELECT 都生成新的 Read View,能看到最新提交数据,会出现不可重复读;RR 整个事务只在第一次快照读时生成一次并复用,保证可重复读。需要注意 MVCC 只解决快照读的幻读,当前读如 SELECT FOR UPDATE 仍需间隙锁防止幻读。
追问与易错
追问方向:
- “Read View 的创建时机?RC 和 RR 有什么区别?”→ RC 每次 SELECT 都创建新的 Read View,能看到其他事务最新提交的数据;RR 只在事务第一次快照读时创建并复用,保证同一事务内多次读取结果一致
- “MVCC 能完全解决幻读吗?”→ 快照读通过 Read View 可以避免幻读;但当前读(SELECT FOR UPDATE / UPDATE / DELETE)需要 Next-Key Lock 间隙锁才能防止其他事务插入新行
- “undo log 版本链怎么清理?”→ InnoDB 的 purge 线程会定期检查,当没有任何活跃事务的 Read View 需要引用某个 undo log 版本时,才会将其清理回收空间
易错点:
- ❌ “MVCC 解决了幻读”——只解决了快照读的幻读,当前读(SELECT FOR UPDATE)仍需间隙锁
- ❌ “每行数据只有一个版本”——undo log 形成版本链,可能有多个历史版本