文章目录

MySQL MVCC多版本并发控制的实现原理

发布于 2026-04-26 23:15:11 · 浏览 2 次 · 评论 0 条

MySQL MVCC多版本并发控制的实现原理

MySQL 的 InnoDB 引擎通过 MVCC(Multi-Version Concurrency Control,多版本并发控制)实现了在不加锁的情况下进行并发读写。这种机制让“读操作”和“写操作”互不冲突,极大地提升了数据库的性能。MVCC 的核心实现依赖于三个组件:隐藏字段、Undo Log 和 Read View。


1. 理解当前读与快照读

在深入 MVCC 之前,必须区分 MySQL 中两种截然不同的读取方式。

  1. 识别 当前读(Current Read)。
    这种读取方式读取的是记录的最新版本,并且会对读取的记录加锁,保证其他事务不能并发修改。常见的操作包括:

    • SELECT ... FOR UPDATE
    • SELECT ... LOCK IN SHARE MODE
    • INSERTUPDATEDELETE 等写操作
  2. 识别 快照读(Snapshot Read)。
    这种读取方式读取的是记录的可见版本(可能是历史版本),不需要加锁。普通的 SELECT 操作就是快照读(在 Read Committed 和 Repeatable Read 隔离级别下)。

MVCC 的作用就是让“快照读”能够读取到符合当前事务隔离级别的历史版本,而无需等待锁的释放。


2. 分析行记录的隐藏字段

InnoDB 在每行数据中,除了用户定义的字段外,还会自动添加几个隐藏字段。这些字段是构建版本链的基础。

  1. 查看 行记录结构。
    每一行数据内部主要包含以下三个对用户不可见的字段:

    • DB_TRX_ID事务 ID
      记录最后一次插入或更新该行数据的 Transaction ID(6 字节)。
    • DB_ROLL_PTR回滚指针
      指向该行记录的上一个版本存储在 Undo Log 中的位置(7 字节)。
    • DB_ROW_ID隐藏主键
      如果表中没有主键,InnoDB 会自动生成这个 ID(6 字节)。

    其中,DB_TRX_IDDB_ROLL_PTR 是 MVCC 的核心。


3. 构建版本链:Undo Log

当一个事务修改某行记录时,InnoDB 不会直接覆盖旧数据,而是将旧数据拷贝到 Undo Log 中。

  1. 执行 更新操作。
    假设一个事务将某行的值从 A 改为 B

    • InnoDB 旧数据 A 复制到 Undo Log 中。
    • 更新 当前行记录的值为 B
    • 设置 当前行记录的 DB_TRX_ID 为当前事务 ID。
    • 设置 当前行记录的 DB_ROLL_PTR 指向 Undo Log 中旧数据 A 的地址。
  2. 重复 修改过程。
    如果另一个事务又将该行从 B 改为 C,则会再次将 B 复制到 Undo Log,并修改当前行的 DB_ROLL_PTR 指向新的 Undo Log 记录。

  3. 形成 链表结构。
    通过 DB_ROLL_PTR,当前行记录和 Undo Log 中的历史版本串联成一个链表,称为“版本链”。链表头是最新记录,链表尾是最早的历史记录。

以下是版本链的简化逻辑图:

graph LR CurrentRow["当前行记录\n(trx_id: 100, value: C)"] UndoLog1["Undo Log 记录 1\n(trx_id: 90, value: B)"] UndoLog2["Undo Log 记录 2\n(trx_id: 80, value: A)"] CurrentRow -- "DB_ROLL_PTR" --> UndoLog1 UndoLog1 -- "DB_ROLL_PTR" --> UndoLog2

4. 判定可见性:Read View

有了版本链,还需要一个规则来判断当前事务应该看到链表中的哪个版本。这个规则就是 Read View(读视图)

当事务发起快照读时,会生成一个 Read View,其中包含以下核心字段:

  • m_ids:生成 Read View 时,当前系统中活跃(未提交)的事务 ID 列表。
  • min_trx_idm_ids 中最小的事务 ID。
  • max_trx_id:生成 Read View 时,系统应分配给下一个事务的 ID(即当前最大事务 ID + 1)。
  • creator_trx_id:生成该 Read View 的事务本身的 ID。

5. 执行版本可见性算法

当事务读取某行数据时,会拿到该行记录最新版本的 DB_TRX_ID,并与 Read View 进行比对,决定是否接受该版本。如果不接受,则沿着 DB_ROLL_PTR 遍历 Undo Log 版本链,直到找到一个可见的版本。

比对规则如下

  1. 判断 DB_TRX_ID 是否等于 creator_trx_id

    • 如果是,说明是自己修改的,可见
  2. 判断 DB_TRX_ID 是否小于 min_trx_id

    • 如果是,说明该版本在 Read View 生成前就已经提交了,可见
  3. 判断 DB_TRX_ID 是否大于或等于 max_trx_id

    • 如果是,说明该版本是在 Read View 生成后才开启的事务修改的,不可见
  4. 判断 DB_TRX_ID 是否在 m_ids 列表中。

    • 如果在,说明生成 Read View 时该事务还未提交(活跃状态),不可见
    • 如果不在,说明生成 Read View 时该事务已经提交了,可见

如果该版本被判定为不可见,则 继续 检查 Undo Log 中的下一个历史版本,重复上述流程,直到找到可见版本或链表结束。


6. 区分 RC 和 RR 隔离级别的差异

MVCC 使得 InnoDB 可以在 Read Committed(RC,读已提交)和 Repeatable Read(RR,可重复读)两个隔离级别下工作。它们的区别在于 生成 Read View 的时机不同

  1. 配置 Read Committed (RC) 级别。

    • 执行 每一次 SELECT 语句时。
    • 系统都会重新生成一个新的 Read View。
    • 这意味着,如果期间有其他事务提交了修改,下一次 SELECT 生成的新 Read View 能够看到这些已提交的最新变更(因为旧事务 ID 不再活跃)。
  2. 配置 Repeatable Read (RR) 级别。

    • 执行 事务中的 第一次 SELECT 语句时。
    • 系统会生成一个 Read View,并在整个事务期间 一直复用 这个 Read View。
    • 这意味着,无论期间其他事务是否提交了修改,由于 Read View 中的 m_ids 等信息是固定的,判定规则永远不会变,从而保证了每次读取到的数据都是一致的。
特性 Read Committed (RC) Repeatable Read (RR)
生成时机 每次查询时生成 事务开始后第一次查询时生成
复用机制 不复用,每次重新生成 整个事务期间复用同一个
可见性 只能看到已提交的数据 只能看到事务开始前已提交的数据

7. 总结核心流程

通过以上步骤,MVCC 的完整闭环逻辑如下:

  1. 开启 事务,发起查询。
  2. 生成 Read View(根据隔离级别决定是新生成还是复用)。
  3. 读取 行记录的 DB_TRX_ID
  4. 比对 Read View 规则。
  5. 决策
    • 如果可见,返回 该行数据。
    • 如果不可见,沿着 DB_ROLL_PTR 去 Undo Log 中找旧版本。
  6. 循环 步骤 3-5,直到找到可见版本。

通过这种机制,MySQL 既保证了读写操作的并发执行,又实现了不同级别的隔离,避免了脏读和不可重复读(在 RR 级别下)。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文