MySQL 间隙锁只会在 RR(可重复读)隔离级别下生效,主要用于避免幻读。它通常出现在当前读场景中,例如 FOR UPDATE、UPDATE、DELETE,并且需要配合索引范围查询,或查询目标记录不存在时才会触发,本质上锁住的是索引记录之间的开区间。

间隙锁只在 RR 隔离级别下生效
在 MySQL 中,间隙锁(Gap Lock)并不会在所有情况下都启用,它有明确的生效条件:首先,只有当事务隔离级别为 REPEATABLE READ 时,InnoDB 才会使用这种锁机制;其次,它只作用于「当前读」相关语句,比如带有 FOR UPDATE、LOCK IN SHARE MODE、UPDATE 或 DELETE 的 SQL。
如果数据库运行在 READ COMMITTED 隔离级别下,InnoDB 通常会退化为只给实际命中的行加行锁,一般不会额外加间隙锁(唯一索引等值查询未命中属于少数例外)。因此,排查 MySQL 间隙锁是否生效时,第一步应先确认当前事务隔离级别:SELECT @@transaction_isolation;。
- 在 RR 级别下,间隙锁是 InnoDB 的默认机制之一,核心目的是防止幻读
- 在 RC 级别下,即使执行
SELECT * FROM t WHERE id > 10 FOR UPDATE,通常也不会锁住(10, +∞)这个间隙范围 - 如需显式设置隔离级别,可使用:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
哪些 SQL 会触发间隙锁?
并不是所有带条件的 FOR UPDATE 查询都会触发 MySQL 间隙锁,关键要看是否扫描到了索引空隙,以及使用的是哪种索引和查询方式。
常见触发场景包括:
SELECT * FROM users WHERE age > 20 AND age < 30 FOR UPDATE;—— 普通索引上的范围查询,通常会加间隙锁,锁定(20,30)区间SELECT * FROM users WHERE id = 100 FOR UPDATE;—— 主键或唯一索引等值查询时,如果id=100这条记录不存在,就会锁住(prev_id, next_id)之间的间隙SELECT * FROM users WHERE name LIKE 'zhang%';—— 如果name字段上有普通索引,前缀匹配本质上仍然属于范围扫描,也可能触发间隙锁UPDATE users SET status=1 WHERE created_at > '2025-01-01';—— 范围更新语句同样可能触发间隙锁,前提是该条件走索引
反过来看,像 SELECT * FROM users WHERE id = 5 FOR UPDATE; 这种场景,如果 id 是主键且该记录真实存在,那么通常只会加记录锁,而不会加间隙锁。
间隙锁锁的是什么?不是记录,是“能插进去的地方”
理解 MySQL 间隙锁的关键在于:它锁住的并不是现有记录本身,而是索引中两个相邻值之间可以插入新数据的位置。比如索引值为 [10, 20, 30],那么对应的间隙就是 (-∞, 10)、(10, 20)、(20, 30)、(30, +∞)。这些位置虽然没有真实行数据,但都具备插入新记录的可能。
典型表现如下:
- 事务 A 执行
SELECT * FROM t WHERE id > 10 AND id < 20 FOR UPDATE;→ 锁住(10, 20)这一段间隙 - 此时事务 B 再执行
INSERT INTO t (id, ...) VALUES (15, ...);→ 会被阻塞,因为 15 落在被锁住的区间内 - 但如果插入的是
INSERT INTO t (id, ...) VALUES (10, ...);或(20, ...),则不会因为间隙锁直接阻塞,因为端点属于已有记录,是否冲突由记录锁决定
需要注意的是,间隙锁对应的是「左开右开」区间,也就是不包含边界值;而临键锁(Next-Key Lock)则是「左开右闭」,即 (10, 20]。可以把它理解为“记录锁 + 前向间隙锁”,这也是 InnoDB 默认更常见的加锁模式。
为什么线上容易因间隙锁卡死或死锁?
单独的间隙锁本身并不是互斥的,多个事务甚至可以同时持有同一个间隙上的间隙锁。真正容易引发阻塞、卡死甚至死锁的,往往是「间隙锁 + 记录锁」组合形成的临键锁,以及不同事务在扫描顺序不一致时发生的竞争。
常见的高风险场景有:
- 查询条件没有索引,例如
WHERE status=1且status字段未建索引 → 会触发全表扫描 → 实际可能锁住整个聚簇索引范围,效果非常接近表锁 - 范围条件过宽,比如
WHERE create_time > '2020-01-01'→ 可能锁住从某条记录一直到+∞的大范围间隙,导致后续插入操作大量阻塞 - 两个事务分别执行
SELECT ... WHERE id > 100 FOR UPDATE和SELECT ... WHERE id > 90 FOR UPDATE,如果扫描顺序或索引访问路径不同,就可能形成循环等待 - 业务中频繁使用
INSERT ... ON DUPLICATE KEY UPDATE,在唯一键冲突时也可能隐式触发间隙锁,尤其是冲突点落在某个间隙范围内时更明显
很多线上问题的难点,并不只是“这条 SQL 是否加了锁”,而是“它到底锁住了多大范围”。排查 MySQL 间隙锁、锁等待和死锁时,重点应查看 INFORMATION_SCHEMA.INNODB_TRX 与 INNODB_LOCKS(MySQL 8.0+ 已移除后者,改用 performance_schema.data_locks),而不能只停留在 SQL 表面写法上。
