mysql范围查询引发gap lock阻塞insert,需通过innodb_trx与data_locks表查等待事务及锁类型,重点分析lock_data区间;优化方案包括建联合索引、避免select for update后直接insert、改用on duplicate key update等。

MySQL 范围查询后 INSERT 被阻塞,大概率是 Gap Lock 或 Next-Key Lock 拦路,不是数据冲突,而是你插的位置“掉进别人锁住的间隙里”了。
怎么确认是范围查询引发的 Gap Lock 阻塞
别看错误日志里有没有 LOCK WAIT —— 它通常不报错,只卡住或超时。直接查锁状态:
- 先找等待中的事务:
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,就正好撞上 - 再顺藤摸瓜查
ENGINE_TRANSACTION_ID对应的原始事务:SELECT TRX_ID, TRX_QUERY, TRX_STARTED, TRX_ISOLATION_LEVEL FROM information_schema.INNODB_TRX WHERE TRX_ID = 'xxx';—— 很可能看到一条未提交的SELECT ... WHERE status BETWEEN 'a' AND 'z' FOR UPDATE或类似范围语句
哪些范围查询最容易拖累后续 INSERT
不是所有 WHERE 都会锁间隙,关键看是否触发 Next-Key Lock 且没走唯一索引:
-
SELECT * FROM t WHERE status = 'pending' FOR UPDATE(status是非唯一二级索引)→ 锁整个匹配值对应的主键间隙,新插入可能被拦 -
UPDATE t SET x=1 WHERE create_time > '2024-01-01'(无索引或索引失效)→ 全表扫描 → 锁(−∞, +∞)整个主键空间 -
SELECT id FROM t WHERE id > 100 ORDER BY id LIMIT 1 FOR UPDATE→ 即使只取一行,InnoDB 仍会锁住(100, next_id)这个间隙 - 空表执行
SELECT * FROM t WHERE id = 5 FOR UPDATE→ 锁(−∞, +∞),后续任何INSERT都可能被卡住
绕过 Gap Lock 的实际办法
核心是让 InnoDB 尽量少扫、少锁间隙,而不是硬扛或降级隔离级别:
- 给范围查询字段建**联合索引**,比如把
(status, id)替代单列status索引,能让锁精准落在具体记录上,而非整个 status 值区间 - 避免“查完就插”模式:不要
SELECT ... FOR UPDATE后紧跟INSERT;改用先查出id,再用INSERT ... ON DUPLICATE KEY UPDATE或INSERT IGNORE—— 它们仍要加意向锁,但至少不扩大间隙范围 - 范围查询确实必要?试试改用唯一条件兜底:
SELECT * FROM t WHERE id IN (SELECT id FROM t WHERE status = 'pending' LIMIT 10) FOR UPDATE,把范围扫描转为等值集合锁定 - 批量插入时,如果目标表无主键或索引薄弱,InnoDB 可能退化为隐式聚簇索引扫描 → 务必确保有主键,且常用查询字段有覆盖索引
INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 也逃不开 Gap Lock
很多人以为加了 IGNORE 就安全了,其实不是:
- 它们底层仍需申请插入意向锁,并检查唯一约束 —— 这个过程会触碰索引间隙,照样可能被已有 Gap Lock 拦住
- 唯一键冲突后若事务没显式
ROLLBACK,InnoDB 可能残留未清理的锁等待链,导致后续插入持续受阻 -
REPEATABLE READ下,哪怕只插一条,只要目标位置落在已锁定间隙内,就会卡住;READ COMMITTED可禁用 Gap Lock,但要接受幻读 —— 切换前务必确认业务能否容忍
Gap Lock 的麻烦在于它不声不响:LOCK_DATA 显示的边界值(比如 100, 110)未必是你 INSERT 的值,而是相邻记录构成的开区间端点。查不到源头事务?八成是它没提交、没回滚、甚至连接断了但事务还挂着 —— 这类“幽灵锁”最耗排查时间。











