Gap Lock与Insert Intention Lock在相同索引间隙上互斥,前者由当前读(如SELECT...FOR UPDATE、范围DELETE/UPDATE)触发并长期持有,后者由INSERT前自动申请用于安全校验,一旦间隙已被Gap Lock锁定,插入意向锁立即被阻塞或报死锁。

Gap Lock 本身不直接“导致”冲突,而是它和 Insert Intention Lock 在同一索引间隙上互斥——只要一个事务持有 (a, b) 的 Gap Lock,其他任何想往这个间隙插数据的事务,INSERT 就会在加插入意向锁时被立即阻塞或报死锁。
Gap Lock 是谁在加?你可能根本没意识到
Gap Lock 不是 INSERT 加的,而是当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)在非唯一索引或范围条件上触发的。比如:
-
SELECT * FROM t WHERE status = 1 FOR UPDATE(status是普通索引)→ 锁住所有匹配status=1记录之间的间隙 -
DELETE FROM t WHERE id BETWEEN 100 AND 200→ 即使没数据,也会锁住(100, 200) - 事务没提交,这些 Gap Lock 就一直挂着,
information_schema.INNODB_TRX里只显示“RUNNING”,但锁本身不可见
Insert Intention Lock 真正的作用不是“占位”,而是“探路”
它不是为了等别人释放锁才加,而是在真正写入前做一次安全校验:这个间隙能不能插?如果已有 Gap Lock,就立刻失败,而不是挂起等待。
- 两个事务同时执行
INSERT INTO t (id) VALUES (150),都试图在(100, 200)加插入意向锁 → 其中一个会被拒绝 - 插入意向锁之间不互斥(插 150 和插 180 可以并行),但和 Gap Lock 互斥
- 它本质是轻量级信号,不是资源占用型锁;冲突高是因为它触发了 InnoDB 对 Gap Lock 的即时检查
唯一索引下更容易伪装成“插入意向锁冲突”
实际卡住你的往往不是插入意向锁,而是唯一键冲突引发的隐式 record lock 等待。
- 事务 A 插入
(uk_col = 123)成功,持X, REC_NOT_GAP锁 - 事务 B 同样插
123,duplicate key 检查阶段要加 S 锁读已存在记录 → 卡在 record lock 上 -
SHOW ENGINE INNODB STATUS常误报为 “waiting for insert intention lock”,但performance_schema.data_locks中看到的是LOCK_MODE = X, REC_NOT_GAP - 这种场景下,Gap Lock 并未参与,但日志表象高度相似,容易误判
最麻烦的地方在于:Gap Lock 没有显式 ID,不暴露在 INNODB_TRX 或常规监控里;你看到的永远是 INSERT 卡住或死锁,而根源可能藏在几分钟前一条看似无害的 SELECT ... FOR UPDATE 里——得靠 performance_schema.data_locks 查 LOCK_MODE LIKE '%GAP%' 才能定位到具体间隙。


















