gap lock导致insert阻塞是因为插入位置落入他人已锁定的索引间隙(如(5,10)),而非数据冲突;在repeatable read下,next-key lock机制使insert需获取插入意向锁,但该锁与gap/next-key锁互斥,故被阻塞。

间隙锁本身不慢,但它会让本可并行的 INSERT 操作被迫排队——不是因为数据冲突,而是因为插入位置掉进了别人已划好的“禁飞区”。
Gap Lock 怎么让 INSERT 卡住
在 REPEATABLE READ 隔离级别下,InnoDB 默认用 Next-Key Lock(记录锁 + 间隙锁)防幻读。哪怕你要插的值当前不存在,只要它落在某个事务已加了 GAP 或 NEXT-KEY 锁的开区间内(比如 (5, 10)),就会被阻塞。
- 典型表现:
INSERT一直不返回、超时报Lock wait timeout exceeded -
SHOW ENGINE INNODB STATUS里能看到waiting for gap before rec insert intention - 主键/唯一索引等值插入(如
INSERT INTO t VALUES (5, 'x'))通常只加记录锁,不触发间隙锁 - 但
INSERT ... SELECT、带子查询的插入、或目标表没主键/合适索引时,InnoDB 可能退化为隐式聚簇索引扫描,间隙范围更难控制
非唯一索引是 Gap Lock 的放大器
二级索引不唯一时,WHERE status = 'pending' 这类查询会锁住整个匹配索引段,后续所有同条件插入都会被拦住——哪怕插入的是不同主键值。
- 例如:
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE可能锁住(pending, pending]对应的所有间隙,导致新订单无法插入 - 如果
status列没有索引,InnoDB 会全表扫描,进而对每个扫描页加锁,效果接近表锁 -
EXPLAIN FORMAT=tree可确认是否走了索引、是否触发Using index condition
怎么快速定位是哪个 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或空值,大概率是全表扫描导致的隐式间隙锁,说明缺少索引
真正容易被忽略的点是:间隙锁不依赖数据是否存在,只依赖索引结构和查询方式;即使你只是 SELECT ... FOR UPDATE 读了一条不存在的记录,也可能锁住一大片空隙。











