insert被gap lock阻塞并非数据冲突,而是插入位置落入他人锁定的索引间隙;需查innodb_trx定位lock wait事务,再通过performance_schema.data_locks筛选lock_mode为gap/next-key且lock_data覆盖目标值的记录。

Gap Lock 阻塞 INSERT 不是因为数据冲突,而是你正试图往别人划好的“禁区”里插数据——哪怕那块地现在空着。
怎么确认 INSERT 是被 Gap Lock 卡住的?
别等超时再猜,直接查系统表定位锁源:
- 先看谁在等锁:
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT' - 拿到
TRX_ID后,查它等的是什么锁:SELECT * FROM performance_schema.data_locks WHERE LOCK_TRX_ID = 'xxx' - 重点过滤
LOCK_MODE为GAP或NEXT-KEY的记录,再看LOCK_DATA字段——比如显示5, 10,说明开区间(5, 10)被锁了 - 如果
LOCK_DATA是NULL或空值,大概率是表没主键/没合适索引,InnoDB 退化成隐式聚簇索引扫描,间隙锁覆盖整个范围
哪些 INSERT 场景最容易触发 Gap Lock?
不是所有插入都会惹上间隙锁,关键看是否“被迫扫范围”:
-
INSERT ... SELECT或带子查询的插入:InnoDB 可能退化为全聚簇索引扫描,间隙范围失控 - 目标表没有主键或唯一索引:InnoDB 用隐式聚簇索引,间隙锁无边界
- 在非唯一二级索引字段上做范围查询(如
WHERE status = 'pending')后紧接INSERT:新行可能落在已被锁住的(a,b)区间内 -
INSERT IGNORE和ON DUPLICATE KEY UPDATE同样逃不开——它们底层仍要申请插入意向锁
绕过 Gap Lock 的实操手段
核心思路是让 InnoDB 尽量只加记录锁,避免扫描间隙:
- 给高频查询条件建联合索引,比如
(status, create_time),比单列status索引更精准控制锁范围 - 避免在非唯一索引字段上范围查询后直接插入;可拆成两步:
SELECT id FROM t WHERE status = ?(走唯一索引),再用INSERT ... WHERE id = ?或SELECT ... FOR UPDATE - 若业务允许且能接受幻读,临时降级隔离级别到
READ COMMITTED—— 此时 Gap Lock 被禁用,但注意INSERT仍可能因唯一键冲突重试 - 对空表或低数据量表并发插入,自增主键不等于免疫死锁;建议改用有序单条提交,减少间隙重叠概率
Gap Lock 的麻烦不在它存在,而在它不报错、不提示具体区间——LOCK_DATA 显示的端点未必是你想插的值,而是相邻记录构成的开区间边界。真正要盯紧的,永远是索引结构 + 查询模式 + 隔离级别这三者的组合效果。











