mysql在rr隔离级别下update不存在的记录一定会加gap lock,这是为防止幻读而设计的刚性机制;只要where条件走唯一索引且记录不存在,innodb即锁定对应索引间隙(如id=5不存在时锁(1,10)),导致insert被阻塞。

MySQL在REPEATABLE READ隔离级别下,UPDATE一条不存在的记录,一定会加Gap Lock——这不是bug,是InnoDB为防止幻读而强制实施的锁行为。
为什么UPDATE没找到行还要锁间隙?
因为InnoDB的锁粒度不是“只锁命中的数据”,而是“锁住WHERE条件扫描到的索引范围”。即使WHERE id = 5查不到任何行,只要id是主键或唯一索引,InnoDB仍会定位到索引中(1, 10)这个空档,并对整个开区间(1, 10)加Gap Lock。
这样做的直接效果是:另一个事务执行INSERT INTO t VALUES (5, 'x')会被阻塞,直到第一个事务提交或回滚。
- Gap Lock锁的是“不存在的位置”,不是真实记录,所以
lock_data显示的可能是边界值(如5),但实际锁住的是(1,5]或(1,10)这类区间 - 它只在当前读语句中生效:
UPDATE、DELETE、SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE - 快照读(普通
SELECT)完全不触发Gap Lock,靠MVCC提供一致性视图
哪些条件组合会触发纯Gap Lock?
必须同时满足以下三点,才会退化出纯Gap Lock(不带Record Lock):
- 事务隔离级别是
REPEATABLE-READ(可通过SELECT @@transaction_isolation确认) - 查询走索引,且是等值条件(
=),但对应记录**完全不存在** - 索引类型是**唯一索引**(主键或
UNIQUE约束);如果是普通索引,等值查询通常加Next-Key Lock(Record + Gap)
反例:UPDATE t SET x=1 WHERE id = 3(id=3存在)→ 只加Record Lock;UPDATE t SET x=1 WHERE status = 'pending'(status是非唯一索引)→ 加Next-Key Lock,不是纯Gap。
怎么验证Gap Lock真正在起作用?
别依赖猜测,用performance_schema.data_locks查实时锁信息最可靠:
- 先在事务A中执行
UPDATE t SET v=1 WHERE id = 7(假设id=7不存在),保持未提交 - 查该事务ID对应的锁:
SELECT LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID = 'xxx' - 若看到
LOCK_MODE为RECORD & GAP或RECORD & NEXT-KEY,且LOCK_DATA显示两个数字(如1, 10),说明间隙已被锁定 -
SHOW ENGINE INNODB STATUS\G里也可能出现lock_mode X locks gap before rec提示,但该输出不易解析,优先用data_locks
绕过Gap Lock的实操思路
Gap Lock不能也不该被“禁用”,但可以规避其影响。核心是让InnoDB认定你只需要锁记录本身:
- 改用
READ COMMITTED隔离级别:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,此时绝大多数场景下Gap Lock被关闭(唯一索引等值未命中除外) - 确保WHERE条件能命中真实记录:例如先
SELECT id FROM t WHERE ... LIMIT 1确认存在,再执行UPDATE - 避免在非必要场景使用
FOR UPDATE或UPDATE扫描大范围间隙;对高频插入场景,考虑用自增主键+有序写入,减少间隙碎片
真正容易被忽略的是:Gap Lock不报错、不显式提示,但它会让INSERT静默等待——直到超时或死锁检测介入。线上遇到莫名插入卡顿,第一反应不该是查慢日志,而是查data_locks和事务状态。











