mysql读写冲突严重时应排查误用当前读:select...for update、lock in share mode、serializable级别下所有select及repeatable read中涉及已修改索引范围的select均不走mvcc快照读;长期未提交事务会导致undo堆积、history list增长、查询变慢。

MySQL 读写冲突严重时,不是靠调大 innodb_lock_wait_timeout 或加索引硬扛,而是要确认是否误用了当前读——快照读(MVCC)本该天然规避读写阻塞,但一写错语句就退化成锁等待。
哪些 SELECT 实际上不走 MVCC 快照读
很多人以为“只要没写 UPDATE 就是快照读”,其实只要语句里带锁提示,InnoDB 就立刻放弃 MVCC 路径:
-
SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE:强制当前读,加行锁或间隙锁,哪怕只查一行也会阻塞其他事务的写 - 在
SERIALIZABLE隔离级别下,所有SELECT都隐式转为当前读,等价于加了LOCK IN SHARE MODE - 事务中先执行了
UPDATE/DELETE,再执行普通SELECT:在READ COMMITTED下仍走快照读;但在REPEATABLE READ下,若该SELECT涉及被修改过的索引范围,可能触发 consistent read 的特殊判定逻辑,实际行为需结合执行计划验证
为什么 REPEATABLE READ 下两次相同 SELECT 结果一样,但 UPDATE 却能改到新插入的行
这不是 bug,是 MVCC 快照可见性与写操作语义分离导致的认知断层:
- 快照读基于事务首次执行
SELECT时生成的Read View,它冻结了“哪些事务已提交、哪些还活跃”的快照,但不冻结数据行本身 -
UPDATE ... WHERE status = 'pending'不读快照,而是走当前读:先定位所有满足条件的最新行(包括其他事务刚INSERT并COMMIT的),再加锁修改 - 所以“读不到新行”和“能更新新行”并存——前者是读视图隔离,后者是写操作面向真实最新状态
长期事务让 MVCC 反而拖慢查询的三个信号
MVCC 依赖 undo log 存历史版本,但这些日志不能无限留着。以下情况会让查询变慢,且不易察觉:
- 存在空闲但未
COMMIT或ROLLBACK的事务(比如应用层连接池泄漏、超时未关闭):它生成的Read View会阻止 purge 线程清理更早的 undo 版本 -
SHOW ENGINE INNODB STATUS中HISTORY LIST长度持续 > 10000:说明 undo 堆积严重,新查询遍历版本链开销上升 - 执行
SELECT时响应时间波动大,尤其在大表高频更新后:可能正被迫扫描长版本链,CPU 使用率异常升高
真正卡住 MVCC 效果的,往往不是并发量,而是那个忘了 COMMIT 的后台脚本、那个配置了 24 小时超时的监控连接、或者那个在事务里调用外部 HTTP 接口迟迟不返回的应用逻辑——它们让 Read View 一直活着,undo 就一直不敢删。











