确认INSERT被GAP锁阻塞需查INNODB_TRX定位LOCK WAIT事务,再通过performance_schema.data_locks筛选LOCK_MODE为GAP/NEXT-KEY且LOCK_DATA覆盖目标值的记录;常见触发场景包括无主键表、INSERT SELECT、非唯一索引范围查询后插入;优化手段有建联合索引、拆分查插操作、有序单条提交等。

MySQL中GAP间隙锁导致并发插入阻塞,根本不是数据冲突,而是你插的位置掉进了别人已划好的“索引禁区”——哪怕那块地现在空着、没任何记录。
怎么确认INSERT正被GAP锁卡住
别等超时或看错误日志,直接查系统表定位真实拦路者:
-
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT'找出卡住的事务 - 拿到
TRX_ID后,执行SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TYPE = 'RECORD' AND (LOCK_MODE LIKE '%GAP%' OR LOCK_MODE = 'NEXT-KEY') - 重点盯
LOCK_DATA字段:若显示5, 10,说明开区间(5, 10)被锁;你要插id = 7,就正好撞上 - 若
LOCK_DATA为NULL或空值,大概率是表没主键/没索引,InnoDB退化成全表扫描,间隙锁覆盖(−∞, +∞)
哪些INSERT场景最容易触发GAP锁阻塞
不是所有插入都会惹上GAP锁,关键看是否“被迫扫范围”:
- 目标表没有主键或唯一索引:InnoDB用隐式聚簇索引,间隙无边界,新插入大概率撞上
-
INSERT SELECT或带子查询的插入:InnoDB可能退化为全聚簇索引扫描,锁范围失控 - 在非唯一二级索引字段(如
status)上做WHERE status = 'pending' FOR UPDATE后紧跟INSERT:新行可能落在已被锁住的(a, b)区间内 -
INSERT IGNORE和ON DUPLICATE KEY UPDATE也逃不开——它们仍要申请插入意向锁,与GAP锁互斥
不降级隔离级别,怎么缩小GAP锁影响范围
核心思路是让InnoDB尽量只加记录锁,少扫、少锁间隙:
- 给高频查询条件建联合索引,比如把单列
status索引升级为(status, id),锁能精准落到具体记录,而非整个status值段 - 避免“查完就插”模式:不要
SELECT ... FOR UPDATE后直接INSERT;改用先查出id,再用INSERT ... WHERE id = ?或SELECT ... FOR UPDATE加锁 - 对空表或低数据量表,并发批量插入时,自增主键不等于安全——建议改用有序单条提交,减少间隙重叠概率
-
EXPLAIN FORMAT=tree确认当前语句是否走索引、是否触发Using index condition,避免索引失效导致全表扫描
真正容易被忽略的是:LOCK_DATA显示的值未必是你想插的值,而是相邻索引记录构成的开区间端点;间隙锁不依赖数据是否存在,只取决于索引结构、查询方式和隔离级别三者的组合效果。


















