在关系型数据库领域,锁机制是解决并发冲突的传统方案。悲观锁会阻塞读写,严重降低并发吞吐;而乐观锁依赖版本号实现冲突检测。InnoDB 采用 MVCC(多版本并发控制),实现了不加锁的一致性读,极大提升数据库并发能力,也是 MySQL 能够实现读不阻塞写、写不阻塞读的核心原理。
很多开发者只知道 MVCC 依靠 Undo Log 实现数据多版本,但不清楚Undo 版本链如何组织、Read View 如何控制数据可见性、RC 与 RR 隔离级别产生幻读差异的底层根源。本文从数据行存储结构出发,逐层拆解 MVCC 完整工作流程,厘清隔离级别底层实现差异,同时梳理线上开发中常见 MVCC 相关陷阱。
InnoDB 行数据隐藏列基础
InnoDB 聚簇索引的每条记录,都会内置三个隐藏字段,是 MVCC 实现的基础:
Undo Log:构建数据历史版本链
2.1 Undo Log 核心作用
Undo Log 称之为回滚日志,两大核心职责:
- 事务回滚:事务失败时,依靠 Undo Log 恢复数据,保证原子性
- MVCC 多版本读取:保存数据修改前的历史快照,供一致性读读取旧版本数据
Undo Log 分为两种类型:
2.2 版本链形成流程
我们模拟一条记录连续更新,演示版本链构建:
- 事务A插入数据,行数据 trx_id=A,roll_ptr 为空
- 事务B更新这条记录:生成新数据,新记录 trx_id=B;roll_ptr 指向事务A对应的 Undo
- 事务C再次更新:生成新版本,roll_ptr 指向事务B的 Undo 记录
最终形成单向链表:最新行记录 → UndoB → UndoA,这条链表就是版本链。当执行一致性读时,MySQL 沿着 roll_ptr 回溯版本链,筛选出当前事务可见的数据版本。
trx_id: C
roll_ptr: → UndoB
trx_id: B
roll_ptr: → UndoA
trx_id: A
roll_ptr: null
2.3 清理机制:Purge 线程
Undo Log 会持续占用存储空间,InnoDB 后台 Purge 线程负责清理过期 Undo。如果业务存在长时间运行的事务,会持续持有旧数据快照,大量 Undo Log 无法被 Purge 回收,造成磁盘暴涨,这是线上经典故障场景。
Read View:可见性判断的核心视图
Read View(读视图)是事务执行一致性读时,用来判断哪个版本数据对当前事务可见的判断依据。Read View 包含四个核心字段:
3.1 通用可见性判定规则
遍历版本链中每一条数据版本的 trx_id,按照以下逻辑判断:
RC与RR隔离级别:Read View 创建时机差异
4.1 READ COMMITTED (RC 读已提交)
缺陷:同一事务内多次查询,结果不一致,会产生不可重复读;不会避免幻读。
4.2 REPEATABLE READ (RR 可重复读,MySQL 默认)
MySQL InnoDB 在 RR 级别通过 MVCC + 间隙锁 共同解决幻读问题。
快照读 vs 当前读(MVCC适用边界)
很多开发者混淆两种读取方式,导致对 MVCC 理解出现偏差:
SELECT ... LOCK IN SHARE MODE;
UPDATE / DELETE
RR隔离级别下幻读深度答疑
标准 SQL 规范中,RR 允许幻读,但是 MySQL InnoDB 在 RR 级别消除了幻读。
- 快照读:依靠 MVCC,读取事务启动时的快照,不会感知其他事务新增数据,天然不存在幻读
- 当前读:依靠 Next-Key Lock(临键锁 = 记录锁 + 间隙锁)锁住区间,阻止其他事务插入新数据,杜绝幻读
线上开发 MVCC 常见踩坑点
长时间运行的事务会持续持有旧 Read View,Purge 线程无法清理 Undo Log,ibdata1 文件持续膨胀。
优化方案:杜绝事务长时间空闲,拆分超大事务。
RC 隔离级别无间隙锁,写入并发高时更容易产生锁冲突,同时 binlog 格式需要合理配置。互联网业务大量使用 RC 隔离级别,需要做好并发控制。
MVCC 优化的是快照读;更新、删除、for update 当前读依旧需要行锁,高并发热点行依旧会出现锁等待。
事务持续数小时会带来严重存储压力,历史版本链过长,查询需要遍历大量 Undo,SQL 性能急剧下降。
结语
MVCC 整套机制由 Undo Log 版本链与 Read View 协同完成:Undo Log 保存数据所有历史版本,构成可回溯的版本链表;Read View 提供统一的可见性判断标准;同时 Read View 创建时机区分了 RC 与 RR 两大隔离级别。
理解 MVCC 底层原理,不仅仅是面试知识点,更能指导业务选型:什么时候选择 RC、什么时候使用 RR;如何规避长事务隐患;区分快照读与当前读,合理优化锁冲突。很多线上 MySQL 死锁、事务超时、磁盘暴涨问题,根源都是没有吃透 MVCC 与 InnoDB 锁机制的配合关系。