间隙锁是啥?一句话说清
间隙锁(
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或走的是非唯一索引时。
