MVCC 原理
hardMySQLMVCCundo logRead View版本链
MVCC 是什么
MVCC(Multi-Version Concurrency Control,多版本并发控制)让读操作不加锁,通过维护数据的多个版本来实现读写不冲突,提高并发性能。
核心思想:每个事务看到的是数据在某个时间点的快照,而不是最新值。
三大组件
1. 隐藏列
InnoDB 为每行数据添加三个隐藏列:
| 隐藏列 | 说明 |
|---|---|
DB_TRX_ID |
最后修改该行的事务 ID |
DB_ROLL_PTR |
回滚指针,指向 undo log 中该行的上一个版本 |
DB_ROW_ID |
自增行 ID(无主键时使用) |
2. undo log 版本链
每次更新数据时,旧版本保存在 undo log 中,通过 DB_ROLL_PTR 形成版本链:
当前行: name=王五, trx_id=300, roll_ptr ──┐
▼
undo log: name=李四, trx_id=200, roll_ptr ──┐
▼
undo log: name=张三, trx_id=100, roll_ptr → NULL
事务 300 将 name 从李四改为王五
事务 200 将 name 从张三改为李四
3. Read View(读视图)
Read View 决定事务能看到哪些版本,包含以下信息:
| 字段 | 含义 |
|---|---|
m_ids |
创建 Read View 时所有活跃(未提交)事务的 ID 列表 |
min_trx_id |
m_ids 中最小的事务 ID |
max_trx_id |
创建时系统分配的下一个事务 ID(最大 ID+1) |
creator_trx_id |
创建该 Read View 的事务 ID |
可见性判断规则
对于版本链中的某个版本(trx_id = T):
T == creator_trx_id → 自己修改的 → 可见 ✅
T < min_trx_id → 已提交的旧事务 → 可见 ✅
T >= max_trx_id → 在 Read View 之后才开始 → 不可见 ❌
T in m_ids → 活跃事务(未提交)→ 不可见 ❌
T not in m_ids → 已经提交 → 可见 ✅
完整示例
事务 100: UPDATE SET name='李四' → 提交
事务 200: UPDATE SET name='王五' → 未提交
事务 300: SELECT name (RR 隔离级别)
Read View: m_ids=[200], min=200, max=301, creator=300
版本链遍历:
1. 当前版本: name=王五, trx_id=200
→ 200 in m_ids → 不可见 ❌
2. 上一版本: name=李四, trx_id=100
→ 100 < min_trx_id(200) → 可见 ✅
结果: name=李四(看不到事务 200 未提交的修改)
快照读 vs 当前读
| 类型 | SQL | 读取版本 |
|---|---|---|
| 快照读 | 普通 SELECT |
通过 MVCC 读历史版本 |
| 当前读 | SELECT ... FOR UPDATE / SELECT ... LOCK IN SHARE MODE / INSERT / UPDATE / DELETE |
读最新版本,加锁 |
生产高频题
MVCC 的实现原理?
通过 undo log 版本链保存数据的历史版本,通过 Read View 的可见性规则判断事务能看到哪个版本。RC 级别每次 SELECT 创建新 Read View,RR 级别首次 SELECT 创建后复用。
快照读和当前读的区别?
快照读(普通 SELECT)通过 MVCC 读取历史版本,不加锁。当前读(SELECT FOR UPDATE、INSERT、UPDATE、DELETE)读取最新版本并加锁,用于需要修改数据的场景。