Record Lock锁存在行,Gap Lock锁不存在的索引间隙;前者为点锁,后者为段锁,仅在REPEATABLE READ生效,Next-Key Lock是其与记录锁组合,用于防止幻读。

Record Lock 锁的是“存在的行”,Gap Lock 锁的是“不存在的空隙”
Record Lock(记录锁)只作用于索引中**真实存在的某一行数据**,比如 SELECT * FROM t WHERE id = 5 FOR UPDATE,就只给 id=5 这条记录加锁;而 Gap Lock(间隙锁)锁的是两个索引值之间的**空档区间**,比如索引里有 id=3 和 id=8,那 (3, 8) 这个开区间就被锁住——哪怕 id=4、id=6、id=7 这些记录当前根本不存在,也不允许其他事务插入。
- Record Lock 是“点锁”,精确到单条索引记录,不扩散
- Gap Lock 是“段锁”,范围由索引排序决定,和具体数据是否存在无关
- Gap Lock 只在
REPEATABLE READ隔离级别下生效,READ COMMITTED下会被自动禁用(InnoDB 会退化为仅 Record Lock) - 没有索引的列上执行范围查询,InnoDB 会退化为表级锁,Gap Lock 失效
为什么 UPDATE/DELETE 范围条件会触发 Gap Lock?
因为 InnoDB 的默认加锁策略是 Next-Key Lock —— 它不是一种独立锁类型,而是 Record Lock + Gap Lock 的组合行为。只要语句走索引、且是范围扫描(比如 WHERE id > 10 AND id ),InnoDB 就会对匹配到的每条记录加 Record Lock,同时对每条记录前后的间隙加 Gap Lock。
- 例如:索引值为 [5, 10, 15, 25],执行
DELETE FROM t WHERE id BETWEEN 10 AND 20,实际锁住的是 (5,10], (10,15], (15,25) 三个区间(注意左开右闭) - 重点:(15,25) 这个间隙被锁,意味着 id=18、id=22 都不能插入,哪怕它们不在
BETWEEN条件内 - 如果想避免 Gap Lock,可改用唯一等值查询(如
id = 15),或临时降级隔离级别(但会引入幻读风险)
常见误判:明明没查到数据,为什么 INSERT 还被阻塞?
这就是 Gap Lock 在“静默工作”。比如表里只有 id=1 和 id=10 两条记录,事务 A 执行了 SELECT * FROM t WHERE id > 5 FOR UPDATE,它虽然没返回任何行,但已对 (5,10) 和 (10,+∞) 两个间隙加了 Gap Lock;此时事务 B 尝试 INSERT INTO t VALUES (7),就会卡住——因为 7 落在 (5,10) 间隙内。
- Gap Lock 不依赖“查到数据”,只依赖“扫描到了哪个索引范围”
-
EXPLAIN看执行计划能帮你判断是否走索引、是否范围扫描,这是预判 Gap Lock 是否触发的关键 - 用
SELECT * FROM performance_schema.data_locks可实时查看当前持有的锁类型和范围(需开启performance_schema) - 死锁日志里如果出现
lock_mode X locks gap before rec,说明就是 Gap Lock 冲突
什么时候 Gap Lock 其实没用?
Gap Lock 不是万能的,它只防“插入”,不防“更新”或“删除”。比如间隙 (3,8) 被锁,别人不能插 id=5,但若 id=5 已存在,别人仍可正常 UPDATE ... SET id = 5(只要没锁该行本身);另外,主键自增列上的插入不受 Gap Lock 影响,因为 InnoDB 对 AUTO_INCREMENT 使用的是专用的 auto-inc lock,与 Gap Lock 无关。
- 非唯一辅助索引上的 Gap Lock 可能比主键更“宽”,因为相同索引值可能对应多行,间隙范围会扩大
- 使用
INSERT ... ON DUPLICATE KEY UPDATE时,先尝试插入,若冲突再更新——这个过程可能先申请 Gap Lock,再升级为 Record Lock,容易引发意想不到的等待 - 线上排查慢 INSERT 时,别只盯着目标表,要顺藤摸瓜查是否有其他事务正在对同一索引范围做范围 SELECT/UPDATE/DELETE
Gap Lock 的边界感很弱:它不显式声明,不报错提示,只在你试图插入“不该插的位置”时突然卡住。真正理解它,得从索引结构出发,而不是从 SQL 表面逻辑猜。

















