ReadView在事务中第一次执行普通SELECT时生成,RC级别每次SELECT都新建,RR级别仅首次创建并复用;它含creator_trx_id、m_ids、min_trx_id、max_trx_id四字段,用于判断行版本可见性。

ReadView 什么时候生成?取决于隔离级别和第一次 SELECT
ReadView 不是在 BEGIN 或 START TRANSACTION 时生成的,而是在事务中**第一次执行普通 SELECT(非锁定读)** 时才创建。这个时间点非常关键,直接影响后续所有快照读的结果。
不同隔离级别行为差异明显:
-
READ COMMITTED:每次SELECT都新建一个 ReadView,所以同一事务内两次查询可能看到不同结果 -
REPEATABLE READ(默认):只在第一次SELECT时生成,之后复用,保证多次读一致 -
READ UNCOMMITTED:根本不用 ReadView,直接读最新行版本,可能脏读
容易踩的坑:事务开启后长时间不查数据,等几秒再 SELECT,此时 ReadView 会包含这期间新启动但未提交的事务 ID —— 这些 ID 就进了 m_ids,导致旧版本无法被 purge。
ReadView 的四个核心字段怎么影响可见性判断
ReadView 本质是一个结构体,含四个关键字段:creator_trx_id、m_ids、min_trx_id、max_trx_id(部分版本叫 m_low_limit_id)。它们不是独立起作用,而是共同参与行版本可见性判断。
判断某行是否可见时,InnoDB 拿该行的隐藏字段 DB_TRX_ID 和这四个字段比对,规则如下:
- 若
DB_TRX_ID == creator_trx_id→ 自己改的,一定可见 - 若
DB_TRX_ID → 在 ReadView 生成前就已提交,可见 - 若
DB_TRX_ID >= max_trx_id→ 在 ReadView 生成后才开启的事务写的,不可见 - 若
DB_TRX_ID在m_ids中 → 写该行的事务还没提交,不可见
注意:这四条是**顺序无关的逻辑或关系**,只要命中一条就确定结果,不需要全部检查。实际代码里是 if-else 链,效率很高。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
为什么长事务会让 undo log 清不掉?关键在 m_ids 膨胀
m_ids 是 ReadView 创建时刻所有活跃事务 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 或连接池配置错误,不是业务需要。
UPDATE 和 SELECT 看到的数据不一致?不是 Bug,是机制设计
很多人困惑:“我在 RR 级别下 UPDATE 了某行,紧接着 SELECT 却看不到新值”。这不是异常,而是 MVCC 分工明确:
-
UPDATE是当前读,走的是最新行版本 + 加锁逻辑,能立刻看到并修改刚提交的数据 - 后续
SELECT是快照读,仍用事务首次生成的 ReadView,只会看到它“认可”的那个版本
换句话说:UPDATE 不受 ReadView 约束,SELECT 受限于 ReadView。两者根本不在同一套可见性体系里运行。想让 SELECT 看到 UPDATE 结果,要么显式 COMMIT 后开新事务,要么强制用 SELECT ... LOCK IN SHARE MODE 触发当前读。

















