MySql MVCC 多版本控制

1. 背景

MVCC(Multi-Version Concurrency Control,多版本并发控制)主要用于 解决高并发下的读写冲突,让:

  • 读操作不用阻塞写操作(读一致性)

  • 写操作不用阻塞读操作(写快照)

在 MySQL 里,

InnoDB 依赖 隐藏字段 + Undo Log + ReadView 来实现:

  1. 每行记录有两个隐藏字段:

    • trx_id:最近一次修改该行的事务 ID

    • roll_pointer:指向 Undo Log 的指针(能找到历史版本)

  2. 事务启动时 会生成一个 ReadView,记录当前活跃事务的 ID 集合。

  3. 查询数据时

    • 如果某行数据的 trx_id 在活跃事务列表中,就沿着 roll_pointer 找历史版本。

    • 如果不在活跃事务中,就说明对当前事务可见。

    这样就能做到:读不阻塞写,写不阻塞读

 

优点:

  • 有 MVCC(InnoDB 默认)

    • 事务内部看到的都是事务开始时的快照。

    • 并发读写不阻塞。

    • 解决了“不可重复读”和“读写冲突”问题。

  • 没有 MVCC

    • 多次读取可能得到不同结果(不可重复读)。

    • 并发写会阻塞读,高并发性能差。

    • 事务逻辑可能出错。

 

2. 具体实现机制

🔹 表中的隐藏列

  InnoDB 每行记录都会自动增加两个隐藏列(假设事务 ID 为 trx_id):

    • DB_TRX_ID:最后一次修改该行的事务 ID

    • DB_ROLL_PTR:指向 Undo Log 的指针(可以找到历史版本)
      (还有个 DB_ROW_ID,用于没有主键时的聚簇索引,这里略)

 

🔹 Undo Log

  Undo Log 保存了 数据被修改前的旧值

  • 插入 → 需要回滚时删除

  • 更新 → 保存旧值(指向前一个版本)

  • 删除 → 保存整行数据(回滚时能插回去)

  这样,InnoDB 就能通过 Undo Log 找到某一行的历史版本。

 

🔹 ReadView(快照读)

  一致性视图(ReadView) 是 MVCC 的核心,它决定了一个事务在“可重复读”或“读已提交”时,能看到哪些版本。
  ReadView 包含四个关键信息:

  1. m_ids:当前活跃事务的 ID 集合

  2. min_trx_id:m_ids 中的最小事务 ID

  3. max_trx_id:下一个将被分配的事务 ID

  4. creator_trx_id:创建该 ReadView 的事务 ID

判断某一行版本是否可见的规则:

    1. 如果 trx_id < min_trx_id → 已提交,可见

    2. 如果 trx_id >= max_trx_id → 未开始,不可见

    3. 如果 trx_idm_ids 内 → 还没提交,不可见

    4. 其他情况 → 可见

3. MVCC 的具体实现过程

🔸 1)查询(快照读)

  select * from user where id = 1;
  • InnoDB 会生成一个 ReadView(快照视图)

  • 通过 隐藏列 + Undo Log 找到符合快照视图的版本

  • 返回的是历史版本,而不是最新的物理数据

  👉 快照读不会加锁,所以效率很高。


🔸 2)更新(当前读)

  update user set age = age + 1 where id = 1;
  • 先加锁(保证写一致性)

  • 将旧值写入 Undo Log(形成历史版本)

  • 更新记录中的 trx_id 为当前事务 ID

  • 指向新的 Undo Log

  👉 这就产生了多版本链条。


🔸 3)删除

  • 并不是直接删除,而是打上删除标记 + 生成 Undo Log

  • 其他事务在快照读时,可能还能看到老版本


4. 不同隔离级别下的表现

  • 读已提交(RC):每次查询都会生成新的 ReadView → 每次都能看到最新提交的数据

  • 可重复读(RR,默认):事务中第一次查询时生成 ReadView,整个事务期间都用这个视图 → 保证可重复读

  • 串行化:MVCC 不起作用,所有读都加锁


5. 小结

  InnoDB MVCC 实现方案 = 隐藏列(DB_TRX_ID + DB_ROLL_PTR) + Undo Log + ReadView

  • 隐藏列:保存版本信息

  • Undo Log:保存旧值,形成版本链

  • ReadView:决定某个事务能看到哪个版本

这样就实现了:

  • 读写并行 → 读走快照,写走最新

  • 高并发下的一致性和性能兼顾

 


一、MVCC 生效条件

  1. 存储引擎必须是 InnoDB

    MyISAM 没有 MVCC。

  1. SHOW TABLE STATUS LIKE 'your_table'\G

    确认 Engine = InnoDB。

  2. 隔离级别必须是 REPEATABLE READ 或 READ COMMITTED

    • SERIALIZABLE 会强制加锁,不走 MVCC。

    • READ UNCOMMITTED 会直接读最新版本,也不依赖 MVCC。

    SHOW VARIABLES LIKE 'tx_isolation'; -- MySQL 8.0 以后用: SELECT @@transaction_isolation;

    如果要修改:

    SET GLOBAL transaction_isolation='REPEATABLE-READ';
  3. 查询必须是快照读(Snapshot Read)

    • 普通 SELECT 会使用 MVCC。

    • 但以下情况会强制加锁,不走 MVCC:

      • SELECT ... FOR UPDATE

      • SELECT ... LOCK IN SHARE MODE

      • UPDATE / DELETE

 


三、验证 MVCC 是否在用

你可以做一个小实验 👇

-- 事务 1
BEGIN;
SELECT * FROM user WHERE id=1;  -- 假设返回 name="Tom"

-- 事务 2
BEGIN;
UPDATE user SET name="Jerry" WHERE id=1;
COMMIT;

-- 事务 1 再查
SELECT * FROM user WHERE id=1;  -- 还是 "Tom",因为用了 MVCC 的快照
COMMIT;

-- 新事务再查
SELECT * FROM user WHERE id=1;  -- 变成 "Jerry"

✅ 总结:

  • InnoDB 默认就启用 MVCC,你不需要额外配置。

  • 只要 Engine=InnoDB + 隔离级别是 RR/RC + 普通 SELECT,就是在用 MVCC。

  • 特殊的加锁读、更新、删除不走 MVCC。

 


1️⃣ 举例

  • 会话 A 开启事务,读取 id=1 的值 → 100

  • 会话 B 开启事务,更新 id=1 的值 → 200 并提交。

  • 会话 A 在事务未提交前再次查询 → 依然是 100(这是 MVCC 的效果)。

  • 会话 A 提交事务后再查询 → 才看到 200


2️⃣ 如果没有 MVCC

如果数据库不支持 MVCC,比如使用传统的 行锁 + 阻塞读 模型(类似 MyISAM 或某些非事务存储引擎):

  1. 会话 A 开启事务,读取 id=1 的值 → 100

  2. 会话 B 尝试更新 id=1:

    • 如果会话 A 持有行锁,会话 B 会阻塞,直到会话 A 提交。

    • 如果没有行锁,会话 B 会直接修改 → 会话 A 再次查询时可能看到 200,这就是不可重复读

换句话说:

事务隔离会话 A 再次读的结果
无 MVCC / 低隔离 可能是 200(被会话 B 更新)
MVCC / REPEATABLE READ 100(事务快照,不受其他事务影响)

3️⃣ 实际并发场景问题

如果没有 MVCC:

  1. 不可重复读

    • 事务中多次读取同一行,得到不同值。

    • 比如用户余额计算时,第一次查询余额 100,第二次查询已经被别人更新为 200 → 会导致计算错误。

  2. 读阻塞写或写阻塞读

    • 传统行锁机制,读操作可能阻塞写,写操作可能阻塞读。

    • 高并发下,性能会严重下降。

  3. 事务逻辑出错

    • 依赖同一事务内数据一致性的业务(库存扣减、订单处理、财务计算)会受到影响。


✅ 总结

  • 有 MVCC(InnoDB 默认)

    • 事务内部看到的都是事务开始时的快照。

    • 并发读写不阻塞。

    • 解决了“不可重复读”和“读写冲突”问题。

  • 没有 MVCC

    • 多次读取可能得到不同结果(不可重复读)。

    • 并发写会阻塞读,高并发性能差。

    • 事务逻辑可能出错。

 

posted on 2025-09-09 16:39  白码一号  阅读(16)  评论(0)    收藏  举报