Gap Lock是锁定索引记录间空隙的锁,仅在REPEATABLE READ级别下生效以防止幻读;RC级别下不启用,仅加行锁,幻读风险重现。

Gap Lock 是什么,为什么它只在 RR 隔离级别下生效
Gap Lock 不是锁某一行,而是锁住索引记录之间的“空隙”。比如表里有 id 值为 1、5、10,那么 (1,5)、(5,10)、(10,+∞) 这些区间就是间隙,Gap Lock 就是锁定其中某个区间,阻止其他事务往里面 INSERT 新记录。
它存在的唯一目的就是防幻读——仅在 REPEATABLE READ 隔离级别下由 InnoDB 自动启用。一旦把隔离级别降到 READ COMMITTED,InnoDB 就不再加 Gap Lock(也不再加 Next-Key Lock),此时 SELECT ... FOR UPDATE 只会加 Record Lock,幻读风险回归。
- Gap Lock 本身不锁任何现有记录,只锁“不能插入”的位置
- 非唯一索引上的等值查询(如
WHERE num = 6)也会触发 Gap Lock,哪怕刚好命中一条记录 - 全表扫描(没走索引)时,InnoDB 会对整个索引范围加 Gap Lock,代价极高,应避免
Next-Key Lock 怎么组合 Record Lock 和 Gap Lock
Next-Key Lock 是 InnoDB 在 RR 级别下的默认加锁行为,本质是「Record Lock + 左侧 Gap Lock」的合体,锁定的是一个左开右闭区间,例如 (5,8]:它既锁住 id=8 这条记录(Record Lock),也锁住 id 在 5 到 8 之间的所有插入位置(Gap Lock)。
关键点在于:它不是固定锁整个范围,而是按实际扫描到的索引路径动态生成。比如执行 SELECT * FROM users WHERE id > 5 FOR UPDATE,InnoDB 会从第一个大于 5 的索引项开始,逐个加 Next-Key Lock,直到超出条件边界。
- 唯一索引 + 等值查询(
WHERE id = 5)会退化为纯 Record Lock,不加 Gap 部分 - 非唯一索引 + 等值查询(
WHERE v1 = 6)仍加 Next-Key Lock,因为可能有多个匹配行,需防幻读 - 查询无结果时(如
WHERE v1 = 100,但表中最大v1是 9),Next-Key Lock 会退化为 Gap Lock,锁定(9,+∞)
为什么并发 SELECT ... FOR UPDATE + INSERT 容易死锁
死锁不是因为两个事务改了同一行,而是它们在不同间隙上“错位加锁”导致循环等待。典型场景:事务 A 执行 SELECT * FROM t WHERE v1 BETWEEN 3 AND 7 FOR UPDATE,锁住 (1,3]、(3,4]、(4,5]、(5,7];事务 B 同时执行 SELECT * FROM t WHERE v1 BETWEEN 5 AND 9 FOR UPDATE,锁住 (4,5]、(5,7]、(7,9]。这时若两者都尝试 INSERT INTO t (v1) VALUES(6),就会因互相等待对方释放 (5,7] 而触发 Error 1213 (40001): Deadlock found when trying to get lock。
- Gap Lock 和 Next-Key Lock 的范围重叠越多,并发冲突概率越高
-
INSERT语句本身不显式加锁,但它在写入前必须获取目标位置的插入意向锁(Insert Intention Lock),该锁与 Gap Lock / Next-Key Lock 冲突 - 用
EXPLAIN FORMAT=tree查看执行计划,确认是否走了预期索引;没走索引就等于全表加锁,死锁风险陡增
如何验证当前语句加了哪种锁
靠猜不如看真实锁状态。MySQL 提供 INFORMATION_SCHEMA.INNODB_TRX、INNODB_LOCKS(8.0.24+ 已移除)、INNODB_LOCK_WAITS 三张表,但最实用的是 sys.innodb_lock_waits 视图(需开启 performance_schema)或直接查 performance_schema.data_locks。
更轻量的方式是在两个 session 中手动构造阻塞:Session A 执行带 FOR UPDATE 的查询后不提交,Session B 尝试插入疑似被锁的值,观察是否 hang 住。再配合 SELECT ENGINE_TRANSACTION_ID, INDEX_NAME, LOCK_TYPE, LOCK_DATA FROM performance_schema.data_locks; 查锁详情。
-
LOCK_TYPE为RECORD表示 Record Lock,LOCK_MODE含GAP表示 Gap Lock,含NEXT-KEY即 Next-Key Lock -
LOCK_DATA显示具体锁定的索引值或间隙边界,如5或5, 8 - 注意:
data_locks中的锁信息只在事务活跃时可见,事务提交/回滚后即消失
真正容易被忽略的,是“非唯一索引的等值查询一定会触发 Gap Lock”这一条。很多人以为只有范围查询才锁间隙,结果在线上突然发现 UPDATE ... WHERE status = 'pending' 把整张表的插入都卡住了——因为 status 是普通索引,且值高度重复,InnoDB 对每个匹配的 status 值都加了 Next-Key Lock,间隙连成一片。


















