先查INNODB_TRX中TRX_STATE='LOCK WAIT'的事务获取TRX_ID,再查performance_schema.data_locks中LOCK_MODE为GAP或NEXT-KEY且LOCK_DATA显示间隙边界(如(5,10))的记录,即可确认INSERT正被Gap Lock阻塞。

怎么确认 INSERT 正被 Gap Lock 卡住
别等超时再猜,直接查系统表定位锁源。Gap Lock 不报错、不提示区间,只让 INSERT 无声卡住或最终抛 Lock wait timeout exceeded。
先看谁在等锁: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)表示开区间已被占用 - 如果
LOCK_DATA是NULL或空值,大概率是全表扫描导致的隐式间隙锁,说明目标表缺索引
哪些 INSERT 场景最容易触发 Gap Lock 阻塞
不是所有 INSERT 都会惹上 Gap Lock,关键看它是否“被迫扫范围”:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
INSERT INTO t VALUES (5, 'x')这种主键/唯一索引等值插入,只加记录锁,不触发间隙锁 -
INSERT ... SELECT或带子查询的插入,InnoDB 可能退化为隐式聚簇索引扫描,间隙范围难控 - 目标表没有主键或合适索引时,InnoDB 用隐式聚簇索引,间隙锁覆盖整个范围
- 非唯一二级索引上的范围查询(如
WHERE status = 'pending')之后紧接INSERT,容易让新插入落在已被锁住的区间内
如何用 system 表快速定位 Gap Lock 冲突点
MySQL 8.0 中,performance_schema.data_locks 是核心依据;5.7 则需交叉比对 INNODB_TRX、INNODB_LOCKS 和死锁日志里的 lock_space/lock_page/lock_rec 物理位置。
- 事务 A 持有
lock_mode = X+lock_type = RECORD,事务 B 等待同一条记录的X锁 → 是行锁冲突,不是 Gap 问题 - 事务 A 持有
lock_mode = X+lock_type = RECORD,事务 B 等待lock_mode = X+lock_type = INSERT_INTENTION,且两者lock_page相同但lock_rec不同 → 典型 Gap + 插入意向锁循环等待 -
INNODB_LOCK_WAITS在 Gap 场景下容易误导,尤其在 5.7 中不记录 GAP 锁等待链,只反映 RECORD 级别等待
绕过 Gap Lock 阻塞的实操要点
核心思路是让 InnoDB 尽量只加记录锁,避免扫描间隙:
- 给常用查询条件建联合索引,比如
(status, create_time),比单列status索引更精准控制锁范围 - 避免在非唯一索引字段上做范围查询后直接插入;可拆成两步:先用唯一索引查出
id,再用WHERE id = ?加锁或插入 - 若业务允许,临时降级隔离级别到
READ COMMITTED(需确保binlog_format = ROW)可禁用 Gap Lock,但要接受幻读风险 - 对空表或低数据量表并发插入,自增主键并不能完全免疫死锁——此时
INSERT ... SELECT或批量插入建议改用有序单条提交,减少间隙重叠概率
真正麻烦的不是 Gap Lock 本身,而是它不暴露锁区间、不区分意图、只靠阻塞现象倒推——必须结合 LOCK_DATA 边界值和物理页定位才能准确定性。

















