mvcc仅对快照读(普通select)生效,不适用于当前读(如select for update、update等);快照读在rc和rr级别下通过readview决定可见版本,当前读则直接读最新数据并加锁。

MVCC让普通SELECT不加锁、不等写,直接读快照——但只在READ COMMITTED和REPEATABLE READ隔离级别下生效,且仅对快照读有效。
快照读 vs 当前读:哪些语句走MVCC?
不是所有SELECT都用MVCC。InnoDB明确区分两类读:
-
SELECT(无锁)→ 快照读 → 走MVCC,读的是ReadView决定的可见版本 -
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE、INSERT→ 当前读 → 不走MVCC,直接读最新行并加锁
常见误解是“MVCC管所有读”,其实它只接管快照读;当前读本质仍是悲观锁机制,会阻塞或被阻塞。
ReadView怎么决定哪条版本可见?
ReadView是事务启动时生成的一组元数据,核心字段包括:m_ids(活跃事务ID列表)、min_trx_id(最小未提交事务ID)、max_trx_id(下一个将分配的事务ID)、creator_trx_id(本事务自己的ID)。判断某行版本是否可见,靠的是行的DB_TRX_ID与这些值的比较:
- 若
DB_TRX_ID==creator_trx_id→ 本事务自己改的,可见 - 若
DB_TRX_IDmin_trx_id → 修改早于ReadView生成前已提交,可见 - 若
DB_TRX_ID≥max_trx_id或DB_TRX_ID∈m_ids→ 修改由未提交/并发事务执行,不可见,需沿rollptr回溯上一版本
这个判断逻辑在每次访问行时实时执行,开销极小,但依赖undo log中完整保留历史版本链。
隔离级别如何影响ReadView生成时机?
同一SQL在不同隔离级别下,可能看到完全不同的数据,根本区别就在ReadView创建时间:
-
READ COMMITTED:每次执行SELECT前都新建ReadView→ 同一事务内多次查询可能看到不同结果(避免脏读,但不保证可重复读) -
REPEATABLE READ:仅在事务中**第一次**SELECT时创建ReadView→ 后续查询复用该视图 → 实现可重复读
注意:REPEATABLE READ下长事务可能拖住undo log无法清理,导致空间膨胀甚至purge线程阻塞。
为什么READ UNCOMMITTED和SERIALIZABLE不用MVCC?
这两个级别主动放弃MVCC路径:
-
READ UNCOMMITTED:直接读最新行,连DB_TRX_ID校验都跳过 → 可能读到未提交数据,MVCC无意义 -
SERIALIZABLE:所有SELECT自动转为SELECT ... LOCK IN SHARE MODE→ 全部变成当前读,加锁串行化 → MVCC被绕过
也就是说,MVCC不是MySQL全局默认行为,而是专为RC和RR设计的优化路径。一旦切换隔离级别,底层读取逻辑就彻底切换。
真正容易被忽略的是:MVCC的“非阻塞”只针对快照读;而任何当前读操作(哪怕只是SELECT ... FOR UPDATE)都会触发行锁,可能引发死锁或锁等待——这点在业务SQL混用快照读与当前读时尤为关键。











