rr隔离级别下当前读必然触发间隙锁,是innodb为保证可重复读语义强制实施的next-key lock策略,包含记录锁与间隙锁组合,覆盖索引间隙以防止幻读。

当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)在 RR 隔离级别下必然触发间隙锁,不是可选行为,而是 InnoDB 为保证可重复读语义强制实施的锁策略。
RR 级别下当前读默认走 Next-Key Lock
MySQL 5.7+ 的 InnoDB 在 REPEATABLE READ 隔离级别中,所有当前读操作都会隐式使用 Next-Key Lock——即记录锁(Record Lock)+ 间隙锁(Gap Lock)的组合。它不是“可能加”,而是引擎级硬编码逻辑。
- 哪怕你只查一个不存在的主键:
SELECT * FROM t WHERE id = 1000 FOR UPDATE,而表里最大id是 999,InnoDB 会锁定(999, +∞)这个间隙 - 范围查询更明显:
SELECT * FROM t WHERE age > 25 FOR UPDATE会锁住所有满足条件的索引区间,以及这些值之间的空隙 - 唯一索引上的
INSERT同样触发:插入前必须先申请对应间隙的锁,防止并发插入破坏唯一性
为什么普通 SELECT 不触发,而当前读必须触发?
快照读(普通 SELECT)靠 MVCC 版本链避免幻读;当前读则必须真实看到并控制数据变更边界,所以需要锁住“可能插入新行”的位置。
- 间隙锁不锁数据行,只锁“不能插入”的空间——比如索引 B+ 树中两个相邻键之间的空白段
- 如果没有间隙锁,事务 A 执行
SELECT * FROM t WHERE val BETWEEN 10 AND 20 FOR UPDATE后,事务 B 就能INSERT INTO t (val) VALUES (15),A 再次执行相同语句就会多出一行,这就是幻读 - 间隙锁本身不解决幻读,它是
Next-Key Lock的一部分;真正起效的是这个组合锁对“插入意向锁(Insert Intention Lock)”的阻塞能力
哪些查询条件实际会扩展出间隙锁?
是否加间隙锁,关键看是否发生“范围扫描”或“唯一性校验失败”,与 SQL 表面写法关系不大。
- 主键等值且记录存在 → 只加
Record Lock,无间隙部分 - 主键等值但记录不存在 → 加
Gap Lock,锁定该值所在间隙(如(5, 10)) - 非唯一索引等值查询 → 即使记录存在,也会锁住该值对应的所有可能位置(因为可能有重复),本质仍是间隙覆盖
- 没走索引的当前读 → 全表扫描退化为锁整个索引范围,即
(−∞, +∞),效果接近表级锁
最容易被忽略的实战细节
间隙锁的边界由 B+ 树索引结构决定,不是 SQL 中写的数值范围。比如 WHERE name > 'Li' 锁的是索引中紧邻 'Li' 的下一个值之间的间隙,而不是字典序上所有大于 'Li' 的字符串。
另外,INSERT ... ON DUPLICATE KEY UPDATE 并不绕过间隙锁——它同样要先检查唯一约束,因此仍会申请对应间隙的锁,死锁风险一点不低。











