readview 是一致性读的判断核心,它不缓存数据而只记录事务id状态,包含 m_ids、min_trx_id、max_trx_id 和 creator_trx_id 四个关键字段,通过比对每行 db_trx_id 与这些字段执行严格可见性判断(先判自改、再判未来事务、再判活跃事务、最后判已提交),从而动态决定版本可见性。

ReadView 是一致性读的判断核心
MySQL 的一致性读(快照读)不是靠“记住某时刻所有数据”实现的,而是靠每个事务启动时生成的 ReadView 动态判断每行数据对当前事务是否可见。它不缓存数据,只缓存一组事务 ID 状态。
一个 ReadView 包含四个关键字段:
-
m_ids:生成时系统中所有活跃(未提交)事务的 ID 列表 -
min_trx_id:即m_ids中最小值,代表“最早未提交事务 ID” -
max_trx_id:下一个将分配的事务 ID,代表“尚未存在的未来事务” -
creator_trx_id:当前事务自己的 ID
每次访问一行时,InnoDB 会检查该行的 DB_TRX_ID(最后修改它的事务 ID),按固定规则比对 ReadView,决定是否返回这个版本——不是“选最新”,而是“选可见”。
可见性判断规则直接决定读到哪一版
对某行记录的某个版本,是否被当前事务看到,完全由以下逻辑判定(顺序执行):
- 如果
DB_TRX_ID == creator_trx_id→ 当前事务自己改的,一定可见 - 如果
DB_TRX_ID → 修改该行的事务在 <code>ReadView创建前已提交,可见 - 如果
DB_TRX_ID >= max_trx_id→ 修改该行的事务在ReadView创建后才开始,不可见 - 如果
min_trx_id → 查 <code>DB_TRX_ID是否在m_ids中:在则未提交,不可见;不在则已提交,可见
这个判断发生在每次行扫描时,不是一次性构建快照。所以即使同一 SELECT 扫描多行,每行的可见性也可能不同(尤其在 READ COMMITTED 隔离级别下,每次查询都新建 ReadView)。
Undo Log 和版本链支撑了“回溯能力”
没有 Undo Log,MVCC 就只是个空壳。InnoDB 每次更新/删除都会把旧值写入 undo log,并用 DB_ROLL_PTR 把各行历史版本串成链表。
当一致性读需要访问旧版本时:
- 从当前行出发,顺着
DB_ROLL_PTR往前找 - 对每个找到的版本,重复执行上述
ReadView可见性判断 - 直到找到第一个“可见”的版本,就返回它
注意:undo log 不永久保留。一旦所有依赖它的 ReadView 都已失效(即没有事务再需要基于它做一致性读),对应 undo 日志段就会被 purge 线程清理。长事务会拖慢 purge,导致 undo log 膨胀和历史版本堆积。
隔离级别差异本质是 ReadView 创建时机不同
所谓“可重复读”不是锁住了数据,而是锁住了 ReadView —— 在 REPEATABLE READ 下,事务第一次执行 SELECT 时创建 ReadView,之后所有快照读复用它;而 READ COMMITTED 每次 SELECT 都新建 ReadView。
这意味着:
- 在
REPEATABLE READ中,即使其他事务中途提交了新版本,你后续查询仍看不到(除非触发当前读) - 在
READ COMMITTED中,两次 SELECT 可能返回不同结果,因为第二次可能看到新提交的版本 -
READ UNCOMMITTED根本不走 MVCC,直接读最新行,不管DB_TRX_ID是否在m_ids中
真正影响一致性的,从来不是“数据有没有多个版本”,而是“你用哪个 ReadView 去看这些版本”。很多线上问题(比如幻读、预期外的不可重复读)其实都源于没意识到 ReadView 的生命周期和复用规则。











