ReadView可见性规则是InnoDB快照读时严格按序执行的四步判断:先判是否为本事务修改,再判是否早于最小活跃ID、是否晚于最大预分配ID,最后查活跃事务列表;生成时机上,RC每次SELECT新建,RR仅首次SELECT创建并复用。

ReadView 的可见性规则不是抽象逻辑,而是 InnoDB 在每次快照读时真实执行的四步判断,直接决定你看到的是哪一行版本。
ReadView 什么时候生成?取决于隔离级别
RC(读已提交)和 RR(可重复读)都用 ReadView,但生成时机完全不同:
- RC 下,
SELECT每执行一次,就新建一个 ReadView —— 所以同一事务内两次SELECT可能看见不同结果(其他事务刚提交) - RR 下,**第一次**
SELECT时生成 ReadView,后续所有快照读复用它 —— 这就是“可重复读”的来源
注意:SELECT ... FOR UPDATE 或 UPDATE 是当前读,不走 ReadView,直接读最新版本并加锁。
可见性判断的四条规则怎么用?
对某行数据的版本,取其 DB_TRX_ID(即修改该版本的事务 ID),与当前 ReadView 的四个字段比对:
- 若
DB_TRX_ID == creator_trx_id:这行就是本事务自己改的,一定可见 - 若
DB_TRX_ID :修改它的事务在本事务开启前就已提交,可见 - 若
DB_TRX_ID >= max_trx_id:修改它的事务在本事务开启后才启动,不可见 - 若
DB_TRX_ID在[min_trx_id, max_trx_id)范围内:查m_ids列表 —— 如果它在列表里,说明修改者事务还没提交,不可见;否则可见
这个顺序不能乱,源码里是严格按此顺序短路判断的。比如 DB_TRX_ID 等于 creator_trx_id 就直接返回可见,不会继续往下比。
为什么长事务会让 m_ids 膨胀、undo log 清不掉?
ReadView 的 m_ids 是生成时刻所有活跃事务 ID 的快照。如果有一个事务迟迟不提交(比如开了事务但忘了 COMMIT),它就会一直留在后续所有新 ReadView 的 m_ids 中。
- 导致旧版本始终“可能被需要”,InnoDB 不敢清理对应的 undo log
- 磁盘空间持续增长,
information_schema.innodb_trx里能看到trx_started时间极早的事务 - 更隐蔽的影响:
min_trx_id被卡住不前进,拖慢整个实例的 purge 进程
所以线上必须监控 TRX_ISOLATION_LEVEL = 'REPEATABLE READ' 且 trx_state = 'RUNNING' 但 trx_started 超过 300 秒的事务 —— 它大概率不是业务逻辑需要,而是 bug 或连接池配置错误。
常见误判点:DB_TRX_ID 为 0 或极大值的情况
DB_TRX_ID 是 6 字节无符号整数,最大值约 2^48 ≈ 281 万亿。但实际中你会遇到两种边界情况:
- 刚插入的行,如果事务未提交,
DB_TRX_ID是当前事务 ID;但如果该行是通过INSERT ... SELECT或批量导入创建的,某些场景下DB_TRX_ID可能为 0(表示“系统初始化写入”,此时按规则视为小于任何min_trx_id,总是可见) - 事务 ID 耗尽后回绕(极罕见),
max_trx_id可能突然变小,导致原本可见的版本变成不可见 —— 这种情况 MySQL 会强制触发全局 flush,但应用层无法感知
真正要警惕的不是这些极端值,而是把 DB_TRX_ID 当作时间戳来排序版本 —— 它只反映事务启动顺序,不等于提交顺序,也不等于物理写入时间。


















