Undo Log 是 MVCC 的唯一数据来源,存储逻辑版本快照并由 DB_ROLL_PTR 串联成版本链;Read View 在事务启动时生成,依据 trx_id 判断版本可见性,实现无锁快照读。

Undo Log 不是 MVCC 的“辅助工具”,而是 MVCC 能跑起来的唯一数据来源;没有 Undo Log,MVCC 就是一句空话。
Undo Log 本质是逻辑版本快照,不是回滚专用日志
很多人误以为 Undo Log 只在 ROLLBACK 时才用,其实它每条记录都承载着“某个事务视角下该行数据应该长什么样”。比如一条记录被事务 A(trx_id=100)更新后,InnoDB 会把旧值 + DB_TRX_ID + DB_ROLL_PTR 写进 Undo Log,并让新记录的 DB_ROLL_PTR 指向它。后续事务读取时,靠这个指针一路往前翻历史版本。
关键点在于:
- Undo Log 存的是逻辑变更(如 “把 name 改为 'alice'” 的反向操作),不是物理页备份
- 每次
UPDATE或DELETE都会生成新 undo record,并串联成链;INSERT的 undo record 只用于回滚,不参与 MVCC 版本链 - undo record 里明确存有生成它的
trx_id,这是后续Read View做可见性判断的依据
MVCC 的“多版本”全靠 DB_ROLL_PTR 串起来
InnoDB 每行数据自带三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。其中 DB_ROLL_PTR 是 MVCC 的脊椎——它不指向磁盘地址,而是一个指向 Undo Log 中某条 record 的偏移量。
当多个事务依次修改同一行:
- 事务 50 更新 → 新记录
DB_TRX_ID=50,DB_ROLL_PTR指向 undo 中事务 50 写入的旧版本 - 事务 80 更新 → 新记录
DB_TRX_ID=80,DB_ROLL_PTR指向 undo 中事务 80 写入的旧版本(即事务 50 的那条记录) - 最终形成一条从最新记录回溯到最老版本的单向链表,称为“版本链”
注意:DB_ROLL_PTR 本身不存时间戳或版本号,它只负责“能找回去”。真正决定“该读哪个版本”的,是下一步的 Read View。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
Read View 才是 MVCC 隔离级别的执行开关
Read View 是事务启动瞬间生成的一个快照结构,包含:m_ids(当前活跃事务 ID 列表)、min_trx_id(活跃事务中最小 ID)、max_trx_id(下一个将分配的事务 ID)、creator_trx_id(当前事务自己的 ID)。
当执行普通 SELECT(快照读)时,InnoDB 沿着版本链逐条检查每个 undo record 的 DB_TRX_ID,按规则判断是否可见:
- 若
DB_TRX_ID == creator_trx_id→ 当前事务自己改的,可见 - 若
DB_TRX_ID → 修改早于当前事务启动,且已提交,可见 - 若
DB_TRX_ID >= max_trx_id或DB_TRX_ID ∈ m_ids→ 修改来自未提交/正在运行的事务,不可见,继续往前查
这个过程不加锁、不阻塞,但代价是:版本链越长,遍历开销越大;长时间运行的事务会让 m_ids 膨胀,导致大量旧 undo record 无法清理,最终触发 undo log overflow。
容易被忽略的两个硬约束
MVCC 不是银弹,它的行为受底层机制强约束:
-
UPDATE/DELETE必须走索引(哪怕是隐式主键),否则会升级为表级锁,破坏 MVCC 前提 - 隔离级别为
READ COMMITTED时,每次SELECT都新建Read View;而REPEATABLE READ下仅第一次SELECT创建,后续复用——这意味着后者能保证可重复读,但前者可能读到新提交的版本 - 所有快照读(
SELECT)都不走UNIQUE或PRIMARY KEY索引时,仍可能触发gap lock,尤其在范围查询中,这和 MVCC 无关,但常被混淆
真正卡住 MVCC 效果的,往往不是理解偏差,而是 undo 表空间配置不合理、长事务未及时结束、或误把当前读(SELECT ... FOR UPDATE)当成快照读去分析。

















