MySQL默认SELECT无锁,但隔离级别影响行为:RC和RR下走MVCC快照读,SERIALIZABLE则隐式加共享锁;当前读(如FOR UPDATE)会绕过MVCC加锁。

MySQL 的 SELECT 默认就是无锁的,但得看隔离级别
MySQL 在 READ COMMITTED 和 REPEATABLE READ 隔离级别下,普通 SELECT(不带 FOR UPDATE 或 LOCK IN SHARE MODE)天然走 MVCC 路径,不加行锁、不阻塞写操作。这不是“配置出来的”,而是 InnoDB 引擎基于 undo log 和 read view 的默认行为。
常见错误现象:SELECT * FROM t WHERE id = 1 看似没锁,但如果在 SERIALIZABLE 隔离级别下,它会隐式升级为 LOCK IN SHARE MODE,导致读写互斥。
- 确认当前会话隔离级别:
SELECT @@transaction_isolation - 生产环境建议固定使用
READ COMMITTED:避免REPEATABLE READ下长事务拖慢 purge 线程,间接影响 MVCC 性能 -
SERIALIZABLE下所有SELECT都等价于加共享锁,彻底放弃 MVCC,别用
为什么某些 SELECT 还是会锁表或锁行?
MVCC 只对“快照读”生效;一旦语句触发“当前读”,就会绕过 MVCC,直接查最新版本并加锁。典型场景包括:
- 显式加锁语句:
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE - 更新类语句的谓词扫描:
UPDATE t SET x=1 WHERE y=2中,所有被WHERE匹配到的记录(无论最终是否修改)都会加临键锁 - 唯一索引等值查询且记录存在时,只加记录锁;但记录不存在时,会在间隙加间隙锁——这也是幻读防控的一部分,不是 bug
容易踩的坑:SELECT ... WHERE non_unique_col = ? 在 REPEATABLE READ 下可能触发间隙锁,即使你只是想“看看有没有”,结果把插入卡住了。
如何验证当前 SELECT 是否走了 MVCC 快照读?
没有直接命令输出“本次 SELECT 是否用了 MVCC”,但可通过组合手段判断:
- 开启 general_log:
SET GLOBAL general_log = 'ON',然后查日志里是否有SELECT后紧跟lock_mode X或lock_mode S—— 有则说明是当前读 - 用
SHOW ENGINE INNODB STATUS\G查TRANSACTIONS部分,观察事务持有的锁类型和数量,对比执行前后变化 - 最简单实操:起两个会话,A 执行
BEGIN; UPDATE t SET a=1 WHERE id=1;(不提交),B 执行SELECT * FROM t WHERE id=1—— 如果 B 立即返回旧值,说明走了 MVCC;如果卡住,说明 B 被阻塞了(大概率是 A 持有锁且 B 发起了当前读)
大范围查询(如分页)时 MVCC 的代价不能忽视
SELECT * FROM t ORDER BY id LIMIT 1000000, 20 这类语句看似无锁,但 MVCC 要回溯 undo log 构造 1000020 行的可见性判断,CPU 和 buffer pool 压力不小。尤其当高并发+长事务共存时,read view 太老,undo log 无法清理,会导致 innodb_history_list_length 持续升高,拖慢整个实例。
- 避免深分页:改用游标式分页(
WHERE id > last_seen_id ORDER BY id LIMIT 20) - 监控关键指标:
SHOW GLOBAL STATUS LIKE 'Innodb_history_list_length',长期高于 10000 就要警惕 - 短事务原则不只是为了减少锁持有时间,更是为了加速 undo 回收 —— 这点比“避免死锁”更影响 MVCC 效率
MVCC 不是银弹,它用空间(undo log)、CPU(可见性判断)和内存(read view、buffer pool 中的老版本页)换并发。真正难的不是“怎么开”,而是“什么时候不该让它一直开着”。











