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(只读事务)

  1. begin;
    • 无 trx_id
    • 无 ReadView
  2. 第一条 SELECT
    • 生成 ReadView
    • creator_trx_id = 0
    • 之后所有 SELECT 复用该 ReadView(RR 核心特性)
  3. 事务结束
    • 全程无 trx_id
    • 无锁、无 undo log,极轻量

场景 B:先 SELECT,后 UPDATE

  1. begin;
  2. 第一条 SELECT
    • 生成 ReadView,creator_trx_id = 0
  3. 执行 UPDATE
    • 分配 trx_id(如 1023)
    • 事务真正激活
  4. 后续 SELECT
    • 仍然复用最初生成的 ReadView
    • 不会重新生成
    • 以此保证 RR 可重复读

场景 C:先 UPDATE,后 SELECT

  1. begin;
  2. 执行 UPDATE
    • 分配 trx_id
  3. 第一条 SELECT
    • 生成 ReadView
    • creator_trx_id = 真实事务ID
  4. 后续 SELECT 一直复用该 ReadView

5. 判断规则

回顾下判断规则

假设数据版本的DB_TRX_ID = trx_id,结合ReadView参数,判断优先级从高到低:

  1. trx_id == creator_trx_id:自己改的数据,可见;

  2. trx_id < min_trx_id:生成该版本的事务已提交,可见;

  3. trx_id >= max_trx_id:生成该版本的事务后于当前事务开启,不可见;

  4. 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,不影响可见性判断。