db_trx_id和db_roll_ptr由innodb自动写入:insert时db_trx_id填当前事务id、db_roll_ptr为null;update/delete时更新db_trx_id为当前事务id,并将旧值写入undo log,db_roll_ptr指向该undo log记录。

DB_TRX_ID 和 DB_ROLL_PTR 是怎么被写入的
每一行 InnoDB 数据在插入或更新时,DB_TRX_ID 和 DB_ROLL_PTR 会由引擎自动填充,开发者完全不可见、也不能干预。关键点在于:DB_TRX_ID 记录的是**最后修改该行的事务 ID**(不是创建事务的 ID),而 DB_ROLL_PTR 指向 Undo Log 中该行的上一个版本。
常见误解是认为“每次 UPDATE 都生成新行并保留旧行”,实际是:InnoDB 在原行位置就地更新,同时把旧值 + DB_TRX_ID + DB_ROLL_PTR 写入 Undo Log,并让当前行的 DB_ROLL_PTR 指向这个旧版本。这就串起了版本链。
- INSERT 不产生前序版本,所以
DB_ROLL_PTR指向空(或 NULL) - UPDATE / DELETE 一定会触发 Undo Log 写入,并更新当前行的
DB_TRX_ID和DB_ROLL_PTR - 如果事务回滚,InnoDB 就用 Undo Log 里的旧值还原,不依赖版本链可见性逻辑
Read View 是什么时候创建的、内容是什么
Read View 是 MVCC 可见性判断的核心,它不是全局共享结构,而是每个事务在首次执行快照读时生成的一次性快照。它的关键字段包括:m_ids(当时活跃事务 ID 列表)、m_low_limit_id(下一个将分配的事务 ID)、m_up_limit_id(最小的活跃事务 ID)和 m_creator_trx_id(本事务自己的 ID)。
隔离级别直接影响 Read View 的生命周期:
-
READ COMMITTED:每次执行SELECT前都新建一个Read View,所以能看到其他事务刚提交的结果 -
REPEATABLE READ:只在事务中第一次快照读时创建Read View,后续所有SELECT都复用它,保证可重复读 -
READ UNCOMMITTED:根本不创建Read View,直接读最新行,MVCC 退化失效 -
SERIALIZABLE:自动将普通SELECT转为SELECT ... LOCK IN SHARE MODE,走当前读路径,MVCC 不参与
版本链如何被遍历、哪些版本会被跳过
当执行快照读时,InnoDB 从当前行开始,顺着 DB_ROLL_PTR 向前遍历版本链,对每个版本检查其 DB_TRX_ID 是否满足 Read View 的可见性规则。判断逻辑不是“找最近的已提交版本”,而是逐个比对:
- 若
DB_TRX_ID == m_creator_trx_id→ 当前事务自己改的,可见 - 若
DB_TRX_ID → 该版本事务在 <code>Read View创建前已提交,可见 - 若
DB_TRX_ID >= m_low_limit_id→ 该版本事务在Read View创建后才启动,不可见 - 若
DB_TRX_ID在m_ids列表中 → 该事务当时未提交,不可见
只要遇到第一个满足条件的版本,就停止遍历并返回——不会继续找“更老”的版本。这也是为什么 REPEATABLE READ 下,即使中间有其他事务提交,你也始终看到同一个快照。
Undo Log 空间不释放会导致什么后果
Undo Log 不是临时内存结构,它真实写入 ibdata1 或独立 undo 表空间(取决于 innodb_undo_tablespaces 配置)。如果长事务一直不提交,它的 Read View 会长期持有,导致版本链无法清理,Undo Log 持续膨胀。
典型现象包括:
-
SHOW ENGINE INNODB STATUS中HISTORY LIST长度持续增长 - 磁盘空间被
ibdata1占满,且OPTIMIZE TABLE无效 - 新事务创建
Read View时扫描版本链变慢,SELECT延迟上升
这不是配置问题,而是业务逻辑问题:避免在事务里做 HTTP 请求、文件读写等耗时操作;监控 INFORMATION_SCHEMA.INNODB_TRX 中 TRX_STARTED 时间过长的事务。











