Skip to content

MySQL 中的 MVCC 是什么? ​

  • 标签:后端、MySQL、数据库

🏛 业务架构师 ​

MVCC(多版本并发控制)是 InnoDB 在 RC/RR 隔离级别下**"读写互不阻塞"**的实现,业务价值先讲清:没有它,每一笔报表查询、每一次后台导出都会锁住交易行,高并发业务根本跑不起来。

机制要点:

  • 每行数据隐藏 trx_id(改它的事务)和 roll_pointer(指向 undo log 里的旧版本),形成版本链;
  • 读操作走快照读,按 ReadView(活跃事务列表)判断版本可见性:在版本链上找到对自己可见的那个版本;
  • RC 与 RR 的差异只在 ReadView 生成时机:RC 每次查询生成新的(所以会不可重复读),RR 在第一次查询生成并沿用整个事务(所以可重复读)。

业务上的两个引申判断:

  1. 快照读不等于实时:RR 下事务开启后再读,读到的是开启时刻的快照。支付结果页"更新后仍显示旧状态"一类问题常源于此,关键路径要强制读主库当前读(for update)或用 RC;
  2. 长事务破坏 MVCC:老 ReadView 让 undo 版本链无法回收,这是我在治理里盯长事务的原因。

一句话:MVCC 用"多存几个旧版本"换来了读写并发,是 InnoDB 支撑 OLTP 业务的基石,理解它才能解释"为什么读到的数据是旧的"这类客诉。

🏗 系统架构师 ​

MVCC 是 InnoDB 在 RC / RR 隔离级别下实现的多版本并发控制:读不加锁、读写不互相阻塞,核心靠三个东西:

  • 隐藏列:每行有 trx_id(最近修改的事务)和 roll_pointer(指向 undo log 里的上一版本);
  • undo log 版本链:一行数据的历次修改通过 roll_pointer 串成链表,历史版本都可在 undo 里还原;
  • ReadView(一致性视图):事务读数据时生成,记录当时活跃(未提交)事务集合。判断某版本是否可见:trx_id 早于视图最小活跃事务 → 可见;在活跃集合内 → 不可见,沿版本链回溯直到找到可见版本。

RC 与 RR 的本质区别就一句:RC 每次查询都生成新 ReadView(所以能看到别人已提交的最新数据),RR 只在第一次查询时生成(所以可重复读)。

架构价值:读写不互斥让"一写多读"的并发模型成立,这是 MySQL 支撑高并发读写的基石,也是我评估任何存储系统并发能力时的第一个问题——悲观锁、MVCC 还是乐观锁,决定了它的读写扩展模型。

💻 全栈开发专家 ​

MVCC = 多版本并发控制,让读写互不阻塞:读不锁写、写不锁读。InnoDB 的实现三件套:

  • 隐藏列:每行有 trx_id(最后修改的事务 id)和 roll_pointer(指向 undo log 里的上一个版本)。
  • 版本链:一行被改多次后,通过 roll_pointer 串成链,最新在头,越往后越旧。
  • ReadView:快照,记录生成时刻"活跃(未提交)事务列表"。判断某版本是否可见:trx_id 是自己改的可见;小于最小活跃 id 可见;在活跃列表里不可见,沿版本链回溯。

RC 与 RR 的唯一区别:RC 每次 select 都生成新 ReadView(所以能看到别人新提交的,不可重复读);RR 只在第一次 select 生成并复用(所以可重复读)。这一句是面试的题眼。

工程上的坑:长事务的 ReadView 一直不放,老版本 undo 链不能清理,导致 undo 膨胀、回滚段增长——innodb_trx 里长事务是必查项。另外 MVCC 只作用于快照读,SELECT ... FOR UPDATE 是当前读,走锁不走版本链,别混淆。