mvcc通过快照读实现读写不阻塞:普通select不加锁,依据read view从版本链读历史版本;而update/delete等当前读操作直接修改最新行并加锁,绕过mvcc。

MVCC 不是用来“主动解决”读写冲突的工具,它本身就是一种让读写天然不冲突的设计机制——普通 SELECT(快照读)根本不会加锁,自然就不会和 UPDATE/DELETE 产生阻塞。
为什么普通 SELECT 不会阻塞写操作?
因为 InnoDB 的快照读不访问最新行数据,而是根据事务启动时生成的 Read View 去版本链里找可见版本。写操作只修改当前最新行并生成新版本,不影响旧版本对其他事务的可见性。
常见错误现象:
误以为“加了索引就能避免锁”,其实锁冲突和 MVCC 是否生效无关;
在 REPEATABLE READ 下执行两次相同 SELECT 得到不同结果,说明用了当前读(如带 FOR UPDATE)或隔离级别被意外降级。
关键点:
– 必须是不带锁的 SELECT 才触发 MVCC;
– READ COMMITTED 和 REPEATABLE READ 下 Read View 的生成时机不同:前者每次查询都新建,后者只在事务第一次快照读时创建;
– SELECT ... FOR UPDATE 或 UPDATE 等属于当前读,绕过 MVCC,直接走行锁 + 最新版本。
哪些操作会绕过 MVCC 直接加锁?
所有当前读操作都不走 MVCC 的快照逻辑,而是读取并锁定最新版本:
– SELECT ... LOCK IN SHARE MODE
– SELECT ... FOR UPDATE
– INSERT、UPDATE、DELETE
– 唯一索引等值查询下的 INSERT ... ON DUPLICATE KEY UPDATE
这些操作会:获取行锁(或间隙锁)、检查最新版本的 DB_TRX_ID、可能触发 next-key lock 防幻读。一旦发生锁等待,就会出现 Lock wait timeout exceeded 错误。
如何确认某条 SELECT 是否走了 MVCC?
最直接的方式是看执行计划和事务行为:
– 检查是否含锁提示:EXPLAIN FORMAT=TRADITIONAL 输出中不含 Extra: Using where; Using index 以外的锁相关字段;
– 在事务中执行两次相同 SELECT,若结果一致且无锁等待,基本可判定为快照读;
– 开启 innodb_status_output_locks = ON 后查 SHOW ENGINE INNODB STATUS,当前读会在 “TRANSACTIONS” 部分显示显式锁信息,快照读则不会;
– 若语句命中唯一索引且条件为等值,InnoDB 可能优化为“一致性非锁定读”,但仍依赖隔离级别和 Read View 规则。
MVCC 的复杂点不在配置,而在理解“读什么版本”由谁决定:不是 SQL 写法,不是索引类型,而是事务启动时刻的 Read View 结构 + 行记录的 DB_TRX_ID 和 DB_ROLL_PTR。很多人调了半天隔离级别,却忘了真正起作用的是那个看不见的视图快照。











