外观
一句话答案
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字节,可选):隐藏主键(无主键时使用)2. undo log(版本链)
每次修改一行,都会在 undo log 中记录旧版本,通过 DB_ROLL_PTR 串成版本链:
当前版本(事务100)→ 上一版本(事务80)→ 再上一版本(事务60)→ ...3. 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)可见性判断规则(判断某版本对当前事务是否可见):
对于版本链中某个版本的 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 找上一个版本,重复判断READ COMMITTED vs REPEATABLE READ 的区别(Read View 生成时机):
| 隔离级别 | Read View 生成时机 |
|---|---|
| READ COMMITTED | 每次快照读都生成新的 Read View → 能看到其他事务已提交的新数据 → 不可重复读 |
| REPEATABLE READ | 整个事务只在第一次快照读时生成一次 Read View → 后续读都用同一个 → 可重复读 |
如何解决脏读:
- undo log 版本链中,未提交事务的修改,其 trx_id 在当前 Read View 的 trx_ids 中
- 根据可见性规则,未提交版本不可见 → 读到的是更早的已提交版本 → 脏读被解决
追问与易错
追问方向:
- Read View 的创建时机?RC 和 RR 有什么区别?(RC 每次 SELECT 创建,RR 事务第一次 SELECT 创建)
- MVCC 能完全解决幻读吗?(快照读可以,当前读需要间隙锁)
- undo log 版本链怎么清理?(purge 线程在没有事务引用时清理)
易错点:
- ❌ "MVCC 解决了幻读"——只解决了快照读的幻读,当前读(SELECT FOR UPDATE)仍需间隙锁
- ❌ "每行数据只有一个版本"——undo log 形成版本链,可能有多个历史版本
💡 记忆锚点
每行数据是一本有修改记录的账本(undo log 版本链),Read View 是一张"哪些事务还没交卷"的名单。拿名单对着账本从新往旧翻,第一个已交卷的版本就是你该看到的。RC 每次读都换新名单(能看到最新提交),RR 整个事务只用第一张名单(保证可重复读)。