间隙锁是InnoDB在REPEATABLE READ级别下锁定索引中两值间空档(如(5,10))以防止幻读的机制,仅对范围查询或非唯一索引等值未命中时触发,不阻塞其他间隙锁但会阻塞插入。

间隙锁是啥?一句话说清
间隙锁(Gap Lock)不是锁某一行,而是锁住索引中两个值之间的“空档”——比如现有记录 id = 5 和 id = 10,那 (5, 10) 这个开区间就被锁住了,别人不能往里插 id = 7 或 id = 8 的新行。它只在 REPEATABLE READ 隔离级别下生效,核心目的就一个:堵住幻读的漏洞。
什么操作会触发间隙锁?
不是所有 SELECT ... FOR UPDATE 都会加间隙锁,得看查询条件和索引结构:
- 范围查询必触发:比如
SELECT * FROM t WHERE id BETWEEN 10 AND 20 FOR UPDATE,会锁住所有命中范围的记录 + 它们之间的间隙 + 边界外的间隙(如(5, 10)、(10, 20)、(20, 25)) - 等值查询但没命中,且字段有**非唯一索引**:比如
WHERE c = 7,而表里c字段只有5和10,那就会锁(5, 10) - 唯一索引等值查询且记录存在 → 只加
RECORD LOCK(行锁),不加间隙锁 -
READ COMMITTED级别下,间隙锁被完全禁用,所以不会出现因间隙锁导致的插入阻塞
怎么确认自己被间隙锁卡住了?
常见现象是:事务 A 执行了带锁的范围查询,事务 B 突然发现 INSERT 卡住不动,SHOW PROCESSLIST 显示 update 或 insert 状态为 Locked,但查不到显式锁表语句。这时可以:
- 查当前锁状态:
SELECT * FROM performance_schema.data_locks;
关注LOCK_MODE列:若看到X,GAP就是间隙锁,X,REC_NOT_GAP是纯行锁,X(无后缀)通常是Next-Key Lock - 检查隔离级别:
SELECT @@transaction_isolation;
必须是REPEATABLE-READ才可能触发 - 看执行计划是否走了索引:
EXPLAIN SELECT * FROM t WHERE c BETWEEN 10 AND 20 FOR UPDATE;
如果type是ALL(全表扫描),InnoDB 可能退化为锁整个索引范围,甚至升级成表锁
间隙锁容易踩的坑
它不冲突、不互斥,但副作用很强:
- 多个事务对同一间隙加
Gap Lock不会阻塞彼此,但只要有一个事务在该间隙内尝试INSERT,就会被全部卡住 —— 表面看没锁竞争,实际是“静默阻塞” - 主键自增列上做范围查询,间隙可能延伸到
(max_id, +∞),导致后续所有INSERT都被拦住,尤其在分页写入或批量导入时很隐蔽 - 业务误以为“没查到数据=没锁”,结果在
WHERE status = 'pending'(非唯一索引)后直接INSERT,却因间隙锁失败,日志里只报超时,不报死锁 - 升级 MySQL 版本后行为可能变化:8.0.18+ 对唯一索引的等值查询优化更强,但非唯一索引的范围扫描加锁逻辑更激进,建议上线前用
data_locks实测
真正难的不是理解间隙锁,而是它总在你没意识到的地方悄悄起作用——尤其是当你的查询看似“只读”,却用了 FOR UPDATE 或走的是非唯一索引时。










