Gap Lock 导致 INSERT 阻塞是因插入位置落入他人锁定的索引间隙,非数据冲突;在 REPEATABLE READ 下触发 Next-Key Lock 时发生,可通过 information_schema.INNODB_TRX 和 performance_schema.data_locks 定位锁源,重点关注 LOCK_MODE 为 GAP 或 NEXT-KEY 且 LOCK_DATA 显示间隙边界(如 (5,10))的记录。

Gap Lock 导致的 INSERT 阻塞不是数据冲突,而是插入位置落在了别人已锁定的索引间隙里。只要隔离级别是 REPEATABLE READ(MySQL 默认),且插入行为触发了 Next-Key Lock 机制,就可能卡住。
怎么确认当前 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或空值,大概率是全表扫描导致的隐式间隙锁,说明目标表缺索引
INSERT 被阻塞的常见触发场景
不是所有 INSERT 都会惹上 Gap Lock,关键看它是否“被迫扫范围”:
-
INSERT INTO t VALUES (5, 'x')这种主键/唯一索引等值插入,只加记录锁,不触发间隙锁 -
INSERT ... SELECT、带子查询的插入,InnoDB 可能退化为隐式聚簇索引扫描,间隙范围难控 - 目标表没有主键或合适索引时,InnoDB 用隐式聚簇索引,间隙锁覆盖整个范围
- 非唯一二级索引上的范围查询(如
WHERE status = 'pending')之后紧接 INSERT,容易让新插入落在已被锁住的区间内
绕过 Gap Lock 的实操方案
核心思路是让 InnoDB 尽量只加记录锁,避免扫描间隙:
- 给常用查询条件建联合索引,比如
(status, create_time),比单列status索引更精准控制锁范围 - 避免在非唯一索引字段上做范围查询后直接插入;可拆成两步:先用唯一索引查出
id,再用WHERE id = ?加锁或插入 - 若业务允许,临时降级隔离级别到
READ COMMITTED(需确保binlog_format = ROW)可禁用 Gap Lock,但要接受幻读风险 - 对空表或低数据量表并发插入,自增主键并不能完全免疫死锁——此时
INSERT ... SELECT或批量插入建议改用有序单条提交,减少间隙重叠概率
Gap Lock 的本质是 MySQL 在 RR 下防幻读的代价,它不报错、不提示具体锁区间,只让 INSERT 卡住或报 Lock wait timeout exceeded。真正麻烦的不是它存在,而是它藏得深、边界模糊——LOCK_DATA 显示的值未必是你 INSERT 的值,而是相邻记录构成的开区间端点。


















