间隙锁是mysql innodb在rr隔离级别下为防止幻读而设计的刚性机制,仅锁定索引记录间的空隙(如(10,20)),不锁记录本身,常见于范围查询、非唯一索引等值查询及不存在记录的唯一索引查询场景。

gap lock 是为防止幻读而生的
MySQL 的间隙锁(gap lock)不是“多此一举”,而是 RR(可重复读)隔离级别下解决幻读的刚性机制。它不锁数据行,只锁索引之间的空隙——比如 (10, 20) 这个开区间,哪怕表里根本不存在 id=15 的记录,这个间隙也会被封锁,阻止其他事务插入 id=15 的新行。
常见错误现象:INSERT 被阻塞、事务 hang 住、死锁日志里出现 lock_mode X locks gap before rec ——这些都不是偶然卡顿,而是间隙锁正在生效。
- 只有在
RR隔离级别下才启用gap lock;RC级别下直接关闭,也就无法避免幻读 -
SELECT ... FOR UPDATE、UPDATE、DELETE这三类语句才可能触发间隙锁;普通SELECT不会 - 唯一索引 + 等值查询(
WHERE id = 100)且该行存在 → 只加record lock,不加gap lock - 唯一索引 + 等值查询但该行不存在(如
WHERE id = 999,而最大 id 是 100)→ 加gap lock锁住(100, +∞)
哪些 SQL 会实际触发 gap lock
触发与否取决于三个要素:隔离级别、索引类型、查询模式。不是所有带 WHERE 的语句都加间隙锁,也不是所有索引都一样。
使用普通索引(非唯一)时,哪怕只查一行,也会加间隙锁;用主键或唯一索引做范围查询(WHERE id BETWEEN 10 AND 20),同样会锁住 (10, 20) 区间。
-
WHERE name LIKE 'a%'(name是普通索引)→ 锁住匹配前缀的所有间隙 -
WHERE status = 1 AND created_at > '2026-07-01'→ 若(status, created_at)是联合索引且满足最左前缀,间隙锁落在该索引扫描范围内 -
WHERE id > 100(id是主键)→ 锁住(100, +∞),即使表里只有 id=101 这一行 - 没走索引的查询(
EXPLAIN显示type = ALL)→ 不是加间隙锁,而是升级为表级锁,影响更大
gap lock 和 next-key lock 到底什么关系
别被术语绕晕:next-key lock 就是 record lock + gap lock 的组合,InnoDB 实际加的几乎总是 next-key lock,而不是纯 gap lock。官方文档里说的 “gap lock” 往往是泛指这种机制下的间隙部分。
它的范围是左开右闭,比如 (10, 20]:包含 20 这条记录(record lock),也包含 10~20 之间所有空隙(gap lock)。
- 当你执行
UPDATE user SET age = 18 WHERE age = 5,而age是普通索引、且表中没有 age=5 的记录 → 加的是纯gap lock,范围是(prev, next),例如(3, 8) - 如果 age=5 存在 → 加
next-key lock,范围变成(3, 5]和(5, 8](InnoDB 向右多扫一个间隙) - 临界点容易错:间隙锁锁定的是“索引结构中的空隙”,不是“业务逻辑上的空档”。比如
number索引值为 [1,6,12],那潜在间隙就是(-∞,1]、(1,6]、(6,12]、(12,+∞]
为什么你改了 SQL 还是被 gap lock 卡住
很多同学调优时删掉 ORDER BY、改成 IN 列表、甚至加了索引,结果事务还是死锁或超时——问题常出在“加锁顺序不可控”和“隐式间隙扩大”上。
-
WHERE id IN (100, 10, 50)→ MySQL 内部仍按主键升序加锁,但你不能依赖它;显式ORDER BY id才能确保顺序一致 - ORM 自动生成的 SQL(如 GORM 的
Where().Update())若未固定字段顺序,where 条件拼接顺序一变,加锁顺序就可能翻转,直接诱发死锁 -
WHERE create_time >= '2026-07-01'走了索引 → 间隙锁落在时间范围上;但写成WHERE DATE(create_time) = '2026-07-01'→ 索引失效,退化成全表扫描加锁 - 长事务会持有间隙锁更久,期间所有试图插入该间隙的语句都会等待,超时后报
Lock wait timeout exceeded
真正难处理的不是“怎么加锁”,而是“锁住了哪一段、谁在等谁、为什么不能并发”。看懂 SHOW ENGINE INNODB STATUS 里的 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: 和 *** (2) HOLDS THE LOCK(S): 段落,比背概念重要得多。











