MVCC RR下trx_id生成时机
RR级别下,实际执行第一个sql语句时,虽然生成了read view,但此时事务并没有真正开启。
1. 事务真正 “开启” 的时机
- 执行 BEGIN / START TRANSACTION 时,只是语法上开启事务
- InnoDB 内部此时并没有真正启动事务
- 也没有分配 trx_id
- 也没有生成 ReadView
- 这叫 事务的延迟激活(lazy startup)。
2. Trx_id 什么时候才真正分配?
只有执行 “写操作” 时,才会分配 trx_id:
- INSERT
- UPDATE
- DELETE
- SELECT … FOR UPDATE(当前读)
只要你还在做普通 SELECT(快照读),InnoDB 就一直不给你分配事务 ID,因为:
只读事务不需要 trx_id,不需要 undo,不需要锁,能省则省。
3. ReadView 在 RR 下到底什么时候生成?
RR 级别:执行第一条 SELECT(快照读)时立即生成 ReadView。
关键点来了:
即使此时还没有 trx_id,ReadView 照样生成!
ReadView 里的 creator_trx_id 会怎么填?
- 还没分配 trx_id 的只读事务 → creator_trx_id = 0
可见性判断规则遇到 creator_trx_id = 0 时:
- 永远不会命中 “自己修改的数据可见” 这条规则
- 因为你根本没改过任何东西
4. 完整真实流程
InnoDB 事务三种执行场景(RR 隔离级别)
场景 A:全程只 SELECT(只读事务)
begin;- 无 trx_id
- 无 ReadView
- 第一条 SELECT
- 生成 ReadView
creator_trx_id = 0- 之后所有 SELECT 复用该 ReadView(RR 核心特性)
- 事务结束
- 全程无 trx_id
- 无锁、无 undo log,极轻量
场景 B:先 SELECT,后 UPDATE
begin;- 第一条 SELECT
- 生成 ReadView,
creator_trx_id = 0
- 生成 ReadView,
- 执行 UPDATE
- 分配 trx_id(如 1023)
- 事务真正激活
- 后续 SELECT
- 仍然复用最初生成的 ReadView
- 不会重新生成
- 以此保证 RR 可重复读
场景 C:先 UPDATE,后 SELECT
begin;- 执行 UPDATE
- 分配 trx_id
- 第一条 SELECT
- 生成 ReadView
creator_trx_id = 真实事务ID
- 后续 SELECT 一直复用该 ReadView
5. 判断规则
回顾下判断规则
假设数据版本的DB_TRX_ID = trx_id,结合ReadView参数,判断优先级从高到低:
-
trx_id == creator_trx_id:自己改的数据,可见;
-
trx_id < min_trx_id:生成该版本的事务已提交,可见;
-
trx_id >= max_trx_id:生成该版本的事务后于当前事务开启,不可见;
-
min_trx_id < trx_id < max_trx_id:在m_ids中(事务活跃)→ 不可见;不在m_ids中(事务已提交)→ 可见。
creator_trx_id = 0 代表:这是一个只读事务,还没有分配真实事务 ID。
在可见性判断逻辑里,MySQL 对这种情况专门做了例外处理:
当 creator_trx_id = 0 时:
- 规则 1(自己修改的数据可见)直接跳过
因为只读事务根本不可能修改数据,所以这条永远不触发
- 规则 2、3、4 照常生效
5. 结论
✔ RR 级别:
ReadView 在第一条 SELECT 时生成,与是否分配 trx_id 无关。
trx_id 只在第一次写操作时分配。
✔ 这就是为什么:
- 只读事务可以极快
- RR 能保证从头到尾数据一致
- 先读后写不会破坏可重复读
- 网上很多文章说 “事务启动时生成 ReadView” 是错的
InnoDB 的事务是延迟激活的,执行 BEGIN 并不会分配 trx_id,也不会生成 ReadView。
在 RR 隔离级别下,ReadView 是在事务中第一条 SELECT 执行时生成,之后全程复用。
而 trx_id 要等到第一次执行写操作或当前读时才真正分配。
只读事务甚至全程没有 trx_id,creator_trx_id 为 0,不影响可见性判断。