insert被gap锁阻塞是因插入位置落入他人锁定的索引间隙(如(100,105)),非数据冲突;通过information_schema.innodb_trx与performance_schema.data_locks可查证lock_mode含gap或next-key及lock_data端点。

INSERT被Gap Lock阻塞不是数据冲突,而是插进了别人划的“禁区”
Gap Lock 本身不锁数据行,只锁索引上的“空隙”。比如事务A执行 SELECT * FROM t WHERE id > 100 FOR UPDATE(id 是主键),InnoDB 会加 Next-Key Lock,实际锁住区间 (100, next_id) —— 这个开区间里哪怕当前没有任何记录,你执行 INSERT INTO t VALUES (105, 'x') 就会被卡住。它不报错、不提示,只等或超时。
怎么确认是Gap Lock在拦路,而不是死锁或唯一键冲突
别看错误信息里有没有 Deadlock found 或 Duplicate entry,先查系统表:
-
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT'找出卡住的事务 - 拿它的
TRX_ID去查performance_schema.data_locks:SELECT ENGINE_TRANSACTION_ID, INDEX_NAME, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID = 'xxx' AND (LOCK_MODE LIKE '%GAP%' OR LOCK_MODE = 'NEXT-KEY') - 重点看
LOCK_DATA:若显示100, 105,说明开区间(100, 105)被锁;你要插102,就正好撞上 - 若
LOCK_DATA是NULL或空值,大概率是目标表没主键/没索引,InnoDB 退化成隐式聚簇索引扫描,间隙覆盖整个范围
哪些INSERT操作最容易触发Gap Lock阻塞
不是所有 INSERT 都危险,关键看是否“被迫扫范围”:
-
INSERT ... SELECT:源表若没走索引,InnoDB 可能全聚簇索引扫描,间隙失控 - 目标表没有主键或唯一索引:InnoDB 用隐式聚簇索引,间隙锁无边界
- 非唯一二级索引字段上做范围查询后紧接
INSERT:例如先SELECT * FROM t WHERE status = 'pending' FOR UPDATE,再插新订单,新行可能落在已被锁住的(pending, pending]区间内 -
INSERT IGNORE和ON DUPLICATE KEY UPDATE同样逃不开——它们底层仍要申请插入意向锁,并参与 Gap Lock 兼容性判断
绕过Gap Lock的核心思路是让InnoDB少扫、少锁间隙
降级隔离级别只是权宜之计,真正稳的解法是控制锁粒度:
- 给高频范围查询字段建**联合索引**,比如把单列
status索引换成(status, id),能让锁精准落到具体记录,而非整个status值段 - 避免“查完就插”模式:不要
SELECT ... FOR UPDATE后直接INSERT;可拆成两步——先用唯一索引查出id,再用INSERT ... ON DUPLICATE KEY UPDATE或显式SELECT ... FOR UPDATE WHERE id = ? - 对空表或低数据量表并发插入,别依赖自增主键“天然安全”:多个事务同时
INSERT可能因页分裂+间隙重叠引发死锁,建议有序单条提交或加应用层排队
Gap Lock 的麻烦不在它存在,而在它不暴露自己——LOCK_DATA 显示的端点未必是你想插的值,而是相邻记录构成的开区间边界。真正要盯紧的,永远是索引结构 + 查询模式 + 隔离级别这三者的组合效果。











