mvcc是innodb专有机制,仅在rc和rr隔离级别下对快照读生效;普通select通过readview与undo版本链可见性判断读取一致旧版本,而当前读(如select for update、dml)则绕过mvcc直接加锁读最新数据。

MVCC 不是 MySQL Server 层的通用机制,而是 InnoDB 存储引擎层专有实现;它只在 READ COMMITTED 和 REPEATABLE READ 隔离级别下生效,且仅对快照读(普通 SELECT)起作用。
为什么普通 SELECT 不加锁却能读到“一致”的旧版本?
因为 InnoDB 在执行快照读时,并不直接访问聚簇索引最新行,而是基于当前事务的 ReadView,沿着该行的 DB_ROLL_PTR 指针回溯 undo log 版本链,逐个比对每个版本的 DB_TRX_ID 是否满足可见性规则。
可见性判断核心逻辑是:
- 若版本的
DB_TRX_ID小于ReadView.min_trx_id:说明该版本由已提交事务生成,可见 - 若版本的
DB_TRX_ID大于等于ReadView.max_trx_id:说明该版本由未来事务生成,不可见 - 若版本的
DB_TRX_ID落在[min_trx_id, max_trx_id)区间内:需查ReadView.m_ids(创建时活跃事务 ID 列表),若存在则说明该事务未提交,不可见;否则 可见
这个过程完全在存储引擎层完成,Server 层只接收最终过滤后的行数据。
RC 与 RR 下 ReadView 创建时机差异直接影响结果
READ COMMITTED 每次执行 SELECT 都会新建一个 ReadView,所以两次查询之间其他事务提交了新版本,第二次就能看到——这就是“不可重复读”来源。
REPEATABLE READ 只在事务中**第一次快照读**时创建 ReadView,后续所有快照读复用同一个视图,因此只要没发生当前读(如 SELECT ... FOR UPDATE),就始终看到同一份快照——这也是它能避免不可重复读的根本原因。
注意:REPEATABLE READ 下幻读仍可能发生(如 INSERT 新记录),InnoDB 通过间隙锁(Gap Lock)和 Next-Key Lock 补充解决,这已超出 MVCC 范畴。
哪些操作会绕过 MVCC 直接走当前读?
任何需要强一致、防并发修改的场景,InnoDB 都会放弃快照读,转而执行当前读——即读取最新版本并加锁。
触发当前读的操作包括:
SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE-
UPDATE、DELETE、INSERT
这些操作不会使用 ReadView 判断可见性,而是直接定位聚簇索引最新记录,并根据语句类型加 S 锁或 X 锁。此时如果该行正被其他事务 X 锁持有,就会阻塞等待——MVCC 的“非阻塞读”在此失效。
undo log 不是无限保留的,版本链可能被截断
InnoDB 会定期 purge 已提交事务的 update undo log,前提是这些日志不再被任何活跃事务的 ReadView 所需。
典型风险场景:
- 一个长事务长时间未提交,其
ReadView一直有效 → purge 线程无法清理该事务开始前生成的旧版本 →undo log持续膨胀,ibdata1文件增长 - 高并发更新 + 长事务共存 → 版本链变长,单行快照读需遍历更多 undo 记录 → 查询延迟上升
所以生产环境必须监控 INFORMATION_SCHEMA.INNODB_TRX 中的 TRX_STARTED 时间,及时干预超时长事务。











