1. 本文范围与参考

本文基于 MySQL 8.0(以 8.0.35 为讨论基线)InnoDBMVCC(多版本并发控制)一致性读 语义,聚焦:

  • ReadView 各字段含义及与 全局事务 id 的关系;
  • 聚簇索引行undo 版本链
  • 本事务修改他事务版本 在可见性判断上的不同路径
  • RR(REPEATABLE READ)RC(READ COMMITTED)ReadView 生命周期 的差异;
  • 事务 id 尚未分配(常为 0) 时,不会以 0 进入活跃事务 id 列表;分配后才以正式 id 参与活跃事务管理。

官方文档入口:

以下对源码字段的命名以教学常用归纳为主,与 storage/innodb 中结构体字段名可能略有出入,语义与手册一致。


2. 为什么需要 MVCC

InnoDB 在不加锁SELECT(一致性非锁定读)下,需要回答:

当前会话应该看到该行哪一个已提交/未提交版本

若直接读最新聚簇记录,会与并发事务的未提交写冲突;若全部加锁,并发度下降。MVCC 通过保留历史版本undo)与可见性判断ReadView),使读操作在多数场景下不阻塞写,写不阻塞读(在可接受的隔离级别语义下)。


3. 一行数据与 undo 版本链

3.1 聚簇索引记录上的关键信息(概念)

聚簇索引上一行(简化):

概念含义
trx_id最近一次修改该行的事务 id(已分配后的全局事务号)。
roll_pointer指向 undo本条修改对应的回滚段信息,并可沿链找到更旧版本。

3.2 版本链(逻辑)

flowchart LR
  subgraph 当前记录
    R0["聚簇行: trx_id = T2\nroll_pointer →"]
  end
  subgraph undo
    U1["undo 记录 1\n旧 trx_id / 旧指针"]
    U2["undo 记录 2\n更旧..."]
  end
  R0 --> U1 --> U2

快照读若判定「当前记录版本」不可见,则沿 roll_pointerundo 中构造/定位更旧版本,重复可见性判断,直到可见或无更早版本。


4. ReadView 是什么

ReadView 是某次一致性读某一时刻对「系统中事务 id 状态」的快照,用于判断:别的会话提交的版本,相对本次读是否应被看见。

4.1 常见字段语义(教学归纳)

字段(常见叫法)含义
creator_trx_id创建本 ReadView 的事务在创建时刻引擎中的 trx_id;若尚未分配,常为 0
m_ids创建 ReadView 瞬间,系统中已分配 trx_id 且仍处于活跃的事务 id 集合(不含「未分配」概念上的 0)。
up_limit_id通常与 m_ids 中最小 trx_id 相关(实现中用于快速比较)。
low_limit_id通常表示:创建 ReadView 时,「下一个将被分配」的 trx_id(有的资料写作 max_trx_id + 1 语义)。≥ low_limit_id 的事务 id 视为在本 ReadView 快照之后才出现。

4.2 事务 id 未分配时:0 不会进入 m_ids

阶段trx->id是否进入 m_ids
尚未向全局申请事务号0(或未占用正式 id)不会0 当作一个正常 trx_id 写入 m_ids
已分配,例如 TTT 进入活跃事务管理;提交/回滚后退出。

因此:活跃 id 列表里只会出现真实分配过的 id,不会出现「0 代表当前会话」 这种项。


5. 可见性判断的两条路径(核心)

对聚簇记录上某版本 trx_id = X

路径 A:是否为本事务修改(优先)

flowchart TD
  A[读取行版本 trx_id = X] --> B{X == 当前事务对象 trx->id ?}
  B -->|是| V[可见:本事务修改的版本\n与 ReadView 中 m_ids 规则无冲突]
  B -->|否| C[进入路径 B:用 ReadView + undo]
  • 相等时:认定为当前事务产生的版本,一致性读需能读到(含未提交,对自己可见)。
  • 此处比较的是 执行该次读时trx->id行上 X不依赖「ReadView 创建时 creator 是否为 0」的数值相等。

路径 B:他人或历史版本(ReadView + undo)

X ≠ 当前 trx->id 的版本,用 ReadView 判断相对快照是否可见;不可见则沿 undo 找旧版。

简化逻辑(与手册/源码教学版一致):

  1. X < up_limit_id 且不在 m_ids 的语义范围内(实现细节略):多表示较早已结束事务的版本,往往可见(需结合删除标记等,此处不展开)。
  2. X ≥ low_limit_id:表示该 id 在 ReadView 创建之后才出现 → 不可见 → 找 undo。
  3. up_limit_id ≤ X < low_limit_idX ∈ m_idsX 不是本事务:他人未提交不可见 → 找 undo。
  4. X 不在 m_ids 且小于 low_limit_id:通常表示已提交可见

教学时常强调:本事务路径 A别人路径 B


6. RR 与 RC 下 ReadView 的生命周期

隔离级别一致性读与 ReadView
RR同一事务内第一次一致性读创建 ReadView,之后复用(同一事务内多次快照读同一快照)。
RC每条一致性读语句可能创建 ReadView(语句级快照)。
sequenceDiagram
  participant S as 会话
  participant RV as ReadView
  Note over S,RV: RR
  S->>RV: 第 1 次一致性读 → 创建 RV1
  S->>RV: 第 2 次一致性读 → 仍用 RV1
  Note over S,RV: RC
  S->>RV: 第 1 条 SELECT → 创建 RVa
  S->>RV: 第 2 条 SELECT → 创建 RVb(新快照)

7. 数值案例:先快照读、再写、再快照读(RR)

前提:全局已有活跃事务 48、49;创建 ReadView 前一刻「下一个将分配的 id」为 50

步骤操作trx->idReadView
1BEGIN未分配 / 0
2第 1 次 SELECT(一致性读)可能仍为 0 或在此路径分配 id(实现相关)创建 RV1;creator 可能为 0;m_ids48、49不含 0
3UPDATE 某行分配 50仍只有 RV1(RR 不重建)
4第 2 次 SELECT50仍用 RV1

UPDATE 后该行:行上 trx_id = 50

  • 路径 A50 == 当前 trx->id (50)可见
  • 若误用「行.trx_id 必须等于 RV1.creator_trx_id」且 creator 仍为 0,会逻辑错误;正确做法是 路径 A当前 trx->id

8. 与「库存超卖」类问题的关系(延伸)

MVCC + RR 只解决的可见性,自动保证业务不变量(如库存 ≥ 0)。并发扣库存需 UPDATE ... WHERE stock >= 1 条件更新、行锁乐观锁等,见业务层设计,本文不展开。


9. 小结

  1. undo 提供版本链ReadView 提供对别人版本快照边界
  2. 本事务修改的版本:行.trx_id当前 trx->id 一致则可见,走「仅 ReadView 活跃表」那一套。
  3. 0 不是合法的全局 trx_id 入 m_ids分配后T 才进入活跃事务管理。
  4. RR 复用第一次 ReadView;RC 语句级新快照。

10. 参考与延伸阅读

  • MySQL 8.0 Reference Manual — InnoDB Transaction Model
  • 源码阅读建议关键词:ReadViewrow_verschanges_visibleMVCC

本文为 DBA 内部学习整理,生产问题请结合 SHOW ENGINE INNODB STATUS、错误日志与官方手册排查。