间隙锁(gap lock)是innodb在rr隔离级别下为防止幻读而引入的开区间锁,仅锁定索引记录间的间隙(如(1,3)),不锁记录本身;它不独立存在,而是next-key lock的一部分,需同时满足rr级别、范围查询、走索引三条件才触发。

间隙锁(Gap Lock)不是独立存在的锁,它只作为 Next-Key Lock 的一部分在 REPEATABLE READ 隔离级别下生效;不满足条件时,它压根不会出现。
Gap Lock 只在 RR 级别 + 范围查询 + 走索引时触发
你执行 SELECT * FROM t WHERE id > 10 FOR UPDATE,只有同时满足以下三个条件,InnoDB 才会加 Gap Lock:
- 当前事务隔离级别是
REPEATABLE-READ(用SELECT @@transaction_isolation确认,不能只靠配置文件) - WHERE 条件是范围查询(如
>、BETWEEN、),等值查询(<code>=)只加 Record Lock,不带 Gap - 字段上有可用索引(主键、唯一索引、普通索引均可),全表扫描或没索引时 InnoDB 不锁间隙
常见错误现象:把隔离级别设成 READ-COMMITTED 后还期待“防止幻读”,结果插入新行完全不阻塞——因为 Gap Lock 根本没启用。
Gap Lock 锁的是开区间,不是记录本身
假设表里有 id = 5, 10, 20 三行,执行 SELECT * FROM t WHERE id > 5 FOR UPDATE,InnoDB 实际加的是 Next-Key Lock,其中 Gap 部分覆盖:
-
(5, 10)—— 不含 5 和 10,但禁止插id = 6~9 -
(10, 20)—— 禁止插id = 11~19 -
(20, +supremum]—— 锁住表末尾所有空隙,禁止插id > 20的行(注意:右闭,这是 Next-Key 的特性)
关键点:Gap Lock 是开区间,INSERT INTO t (id) VALUES (10) 不冲突(10 已存在,走 Record Lock),但 VALUES (15) 会被阻塞,因为它落在 (10, 20) 内。
真正拦截插入的是 Insert Intention Lock 冲突
Gap Lock 不直接“拦 INSERT”,而是让插入事务申请 Insert Intention Lock 时失败:
- 事务 A 持有
(5, 10)上的 Gap Lock - 事务 B 执行
INSERT INTO t (id) VALUES (7),必须先获取(5, 10)上的Insert Intention Lock - 该意向锁与 A 的 Gap Lock 冲突 → B 等待或超时
容易被忽略的一点:即使你用 INSERT ... ON DUPLICATE KEY UPDATE,同样要走这套检查逻辑,不存在“绕过间隙锁”的捷径。
验证 Gap Lock 是否真起了作用,别只看隔离级别
光确认 @@transaction_isolation 是 REPEATABLE-READ 不够,得查锁的实际行为:
- 在事务中执行目标 SQL(如
SELECT ... FOR UPDATE)后,记下trx_id - 查
performance_schema.data_locks:SELECT LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID = 'xxx' - 如果
LOCK_MODE出现RECORD & GAP或RECORD & NEXT-KEY,说明 Gap 部分已生效
最易踩的坑:误以为“锁了某条记录”就等于“锁住了范围”——Gap 锁的边界由 B+ 树索引结构决定,和 SQL 中写的数值范围不完全对应,比如非唯一索引上相同值的多行会导致间隙划分更细。











