MVCC

tim-qtp...大约 3 分钟MySQL数据库

MVCC 指的是多版本并发控制,是一种并发控制机制。简单来说,就是给我们的 MySQL 数据拍个“快照”,定格某个时刻数据库的状态。允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。

在 MySQL 中,MVCC 是通过隐藏字段undo log版本链ReadView 机制来实现的。

多版本:指mysql维护着行数据的多个版本

并发控制:在多个事务同时操作某一行记录时mysql控制返回多个版本的行记录中的某个版本。

1. 隐藏字段:每条记录的小秘密

  • DB_TRX_ID:记录最后一次修改这条数据的事务 ID。
    • 比如事务 A 修改了这条数据,那么这条数据的 DB_TRX_ID 就是事务 A 的 ID。
  • DB_ROLL_PTR:指向这条数据的旧版本(历史版本)的指针。
    • 比如事务 A 修改了数据,旧版本的数据会被存到 Undo Log 里,DB_ROLL_PTR 就是指向这个旧版本的“地址”。

2. Undo Log 版本链:数据的时光机

  • 每次修改数据时,MySQL 不会直接覆盖旧数据,而是把旧数据存到一个叫 Undo Log 的地方。
  • 这些旧数据通过 DB_ROLL_PTR 连成一条链,就像一个时光机,可以找到这条数据的所有历史版本。
  • 比如:
    • 事务 A 把 balance 从 1000 改成 900,旧版本 1000 被存到 Undo Log。
    • 事务 B 又把 balance 从 900 改成 800,旧版本 900 也被存到 Undo Log。
    • 这时候,DB_ROLL_PTR 就指向了一个版本链:800 → 900 →1000。

3. ReadView:决定你能看到什么

  • 当一个事务想读数据时,MySQL 会为它创建一个 ReadView
  • ReadView 就像是一个“快照”,决定了这个事务能看到哪些数据版本。
  • ReadView 包含以下信息:
    • 活跃事务列表:当前正在运行的事务 ID。
    • 最小事务 ID:当前系统中最老的事务 ID。
    • 最大事务 ID:当前系统中最新的事务 ID(:事务 ID 的最大值加一。换句话说,它是下一个将要生成的事务 ID)。
    • 创建者事务 ID:创建这个 ReadView 的事务 ID。

用一个例子来说明整个过程:

场景

  • users 中有一条数据:id=1, name='Alice', balance=1000
  • 事务 A(ID=100)把 balance 从 1000 改成 900。
  • 事务 B(ID=101)想读取 balance 的值。

步骤 1:事务 A 修改数据

  • 事务 A 把 balance 从 1000 改成 900。
  • MySQL 做了两件事:
    1. 把旧数据 balance=1000 存到 Undo Log。
    2. 更新当前数据的 DB_TRX_ID=100(事务 A 的 ID),DB_ROLL_PTR 指向 Undo Log 中的旧版本(1000)。

步骤 2:事务 B 读取数据

  • 事务 B 想读取 balance 的值。
  • MySQL 为事务 B 创建一个 ReadView:
    • 活跃事务列表:[100](事务 A 还在运行)。
    • 最小事务 ID:100。
    • 最大事务 ID:101。
    • 创建者事务 ID:101(事务 B 的 ID)。
  • 事务 B 开始读取数据:
    1. 检查当前数据的 DB_TRX_ID=100
    2. 判断 DB_TRX_ID 是否可见:
      • 如果 DB_TRX_ID 小于最小事务 ID(100 < 100?不成立),说明数据是事务 B 开始之前提交的,可见。
      • 如果 DB_TRX_ID 在活跃事务列表中(100 在 [100] 中),说明数据是未提交的,不可见。
      • 如果 DB_TRX_ID 大于等于最大事务 ID(100 >= 101?不成立),说明数据是事务 B 开始之后创建的,不可见。
    3. 发现 DB_TRX_ID=100 在活跃事务列表中,说明数据是未提交的,不可见。
    4. 通过 DB_ROLL_PTR 找到旧版本的数据 balance=1000,返回给事务 B。

步骤 3:事务 A 提交

  • 事务 A 提交后,数据的 DB_TRX_ID=100 变成已提交状态。
  • 如果事务 B 再次读取数据:
    • 检查 DB_TRX_ID=100,发现它不在活跃事务列表中,说明数据已提交,可见。
    • 直接返回当前数据 balance=900