ReadView可见性判断必须严格按四步顺序执行:先判是否本事务修改,再判是否在ReadView生成后启动,再查活跃事务列表,最后默认可见;顺序颠倒会导致误判。

ReadView可见性判断的四步顺序不能颠倒
InnoDB对某行版本是否可见的判断,是硬编码在源码里的短路逻辑,必须严格按顺序执行。跳过或调换步骤会导致误判——比如把DB_TRX_ID == creator_trx_id放到后面,就可能出现“自己刚INSERT的数据SELECT不到”的诡异现象。
真实判断流程如下:
- 先比
DB_TRX_ID == creator_trx_id:是本事务改的,直接可见,不往下走 - 再比
DB_TRX_ID :说明修改者早于当前事务启动且已提交,可见 - 接着比
DB_TRX_ID >= max_trx_id:说明修改者在当前ReadView生成后才启动,不可见 - 最后查
m_ids列表:若DB_TRX_ID在其中,说明修改事务当时未提交,不可见;否则可见
RC和RR下ReadView生成时机决定行为差异
同一句SELECT在RC和RR下可能返回不同结果,根本原因不是SQL本身,而是背后ReadView的生命周期。
RC场景下:SELECT每次执行都调用innobase_get_read_view()新建一个ReadView,所以事务A中两次SELECT之间,若事务B提交了,第二次就能看到新值。
RR场景下:ReadView只在事务内**第一次**快照读(如普通SELECT)时创建,后续所有快照读复用它。即使事务B中途提交,只要它的trx_id落在当初m_ids里或[min_trx_id, max_trx_id)区间内,就一直不可见。
注意:BEGIN或START TRANSACTION不触发ReadView创建;显式开启事务后先做UPDATE再SELECT,ReadView仍等到SELECT时才建。
长事务卡住min_trx_id会导致undo log无法清理
ReadView的m_ids是生成时刻所有活跃事务ID的快照。如果有一个事务长时间不提交(比如开了事务但忘了COMMIT),它就会持续留在后续所有新ReadView的m_ids中。
后果很实际:
- 旧版本数据始终“可能被需要”,InnoDB不敢回收对应
undo log,磁盘空间持续增长 -
min_trx_id被卡住不前进,拖慢整个实例的purge线程 -
information_schema.innodb_trx里能看到trx_started极早、状态仍是RUNNING的事务
线上必须监控:TRX_ISOLATION_LEVEL = 'REPEATABLE READ'且trx_state = 'RUNNING'但trx_started超过300秒的事务——这大概率不是业务需要,而是bug或连接池配置错误。
DB_TRX_ID为0或极大值时的边界处理
DB_TRX_ID是6字节无符号整数,最大值约281万亿,但实际中会遇到两类特殊值:
刚插入的行,若事务未提交,DB_TRX_ID就是当前事务ID;但如果该行来自INSERT ... SELECT或批量导入,某些场景下DB_TRX_ID可能为0——此时InnoDB将其视为“已提交的系统事务”,按DB_TRX_ID 规则处理,通常可见。
另一种情况是DB_TRX_ID极大(比如接近2^48),它大概率落在DB_TRX_ID >= max_trx_id分支,直接判定为不可见——除非你真有上百万并发事务同时跑,否则这基本意味着该版本由未来事务生成,逻辑上就不该被当前ReadView看到。
真正容易踩坑的是:没意识到DB_TRX_ID == 0不是“无事务”,而是“系统内部事务”,别拿它当空值过滤。


















