mysql的repeatable read通过next-key lock(record lock+gap lock)在索引范围查询时自动防幻读,但仅当走索引、满足范围条件且配置干净时生效;否则间隙锁失效,幻读仍可能发生。

MySQL 的 REPEATABLE READ 隔离级别(InnoDB 默认)在绝大多数场景下**已经能防止幻读**,不是靠“手动补救”,而是靠底层的 Next-Key Lock 机制自动生效。但这个机制有前提、有边界,踩错就失效。
为什么 REPEATABLE READ 在 MySQL 里能防幻读?
这不是标准 SQL 定义的“可重复读”,而是 InnoDB 特有的实现:它把 Record Lock(锁行)和 Gap Lock(锁间隙)合起来,形成 Next-Key Lock。比如查询 WHERE id BETWEEN 10 AND 20,它不仅锁住已存在的 id=10、15、20 这几行,还会锁住 (10,15)、(15,20) 这些“空档”,让其他事务插不进新行。
关键点:
-
Next-Key Lock只在REPEATABLE READ+ 索引字段范围查询 时触发(如>、BETWEEN、IN等) - 主键等值查询(
WHERE id = 100)只加Record Lock,不锁间隙 → 不防幻读 - 没走索引的查询(如
WHERE status = 'pending'且status无索引)→ 可能退化为表锁或干脆不锁间隙
怎么确认 Next-Key Lock 是否真起了作用?
不能只看隔离级别设了 REPEATABLE READ 就认为万事大吉。必须验证锁是否按预期加了:
- 执行
SELECT * FROM performance_schema.data_locks,找LOCK_MODE值含NEXT-KEY或GAP的记录 - 用
SHOW ENGINE INNODB STATUS\G查TRANSACTIONS段,若看到lock_mode X locks rec but not gap,说明只锁了行、没锁间隙 - 在事务中执行
SELECT ... FOR UPDATE范围查询后,另起一个会话尝试INSERT到该范围 —— 若被阻塞,才是Next-Key Lock生效
哪些操作会让幻读“漏网”?
开发中最常掉坑的地方,往往不是不会配,而是写法或结构破坏了锁机制:
- 查询条件用了函数或表达式,比如
WHERE YEAR(created_at) = 2024→ 索引失效 → 间隙锁不生效 - 联合索引顺序没对上,比如索引是
(a,b),却查WHERE b = 1→ 无法利用索引 → 间隙锁大概率失效 - MySQL 8.0+ 旧配置残留:
innodb_locks_unsafe_for_binlog = ON(已废弃但可能还在 my.cnf 里)→ 直接禁用间隙锁 - 事务里先
SELECT再INSERT或UPDATE,中间没加锁 → 其他事务趁机插入,幻读就发生了
实在绕不开,有哪些务实替代方案?
当业务逻辑复杂、索引难优化,或必须支持无索引字段的范围查询时,别硬扛 Next-Key Lock,换思路:
- 改用
SELECT ... FOR UPDATE显式加锁:它在REPEATABLE READ下也会触发Next-Key Lock,比普通SELECT更可靠 - 应用层加分布式锁(如 Redis),按业务键(如
category_id)粒度控制并发写入,但引入了外部依赖 - 把“校验+插入”改成原子操作:用
INSERT ... ON DUPLICATE KEY UPDATE或带唯一约束的INSERT IGNORE,靠数据库约束兜底 - 仅在极端一致性要求场景(如金融核对)才升到
SERIALIZABLE,但得接受并发吞吐暴跌
真正容易被忽略的,不是“要不要加锁”,而是“锁有没有落在该锁的位置上”——索引是否命中、查询是否范围、配置是否干净,三者缺一不可。











