Next-Key Lock 阻塞 INSERT 的根本原因是插入意向锁与已存在的 Gap/Next-Key 锁冲突;InnoDB 不兼容二者,直接阻塞。INSERT 前必须申请插入意向锁,若间隙已被其他事务加 Gap 锁,则立即失败。

Next-Key Lock 会阻塞 INSERT,根本原因不是 INSERT 自己加了什么重锁,而是它必须申请的 INSERT intention lock 与已存在的 NEXT-KEY 或 GAP 锁冲突——InnoDB 明确拒绝兼容,直接阻塞或报死锁。
为什么 INSERT 没写数据就卡住?
因为 INSERT 执行前必须先申请插入意向锁,这是一种声明“我要往某间隙插”的轻量级锁。但它和 Gap Lock / Next-Key Lock 作用于完全相同的索引间隙(比如 (10, 15)),而 InnoDB 规定:只要已有事务持有了该间隙的 Gap 锁,后续任何插入意向锁申请都立刻失败,不等待、不重试。
常见现象:
-
SHOW ENGINE INNODB STATUS显示insert intention waiting,但查不到谁在 hold 锁 - 表里根本没有
id = 12这条记录,INSERT INTO t (id) VALUES (12)却卡住 - 事务 A 执行过
SELECT * FROM t WHERE status = 'pending' FOR UPDATE后未提交,事务 B 插入任意status = 'pending'的行都会被拦
哪些操作会悄悄留下 Gap 锁?
Gap 锁本身不显式暴露,也不出现在 INNODB_TRX 中,但它真实存在且长期持有。最容易被忽略的是“看起来安全”的只读加锁语句:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
SELECT ... FOR UPDATE或LOCK IN SHARE MODE在非唯一索引上执行后未提交 - 空表执行
SELECT * FROM t WHERE id = 5 FOR UPDATE→ 锁整个主键空间(-∞, +∞) - 批量导入脚本用
SELECT ... FOR UPDATE做幂等校验,高并发下多个事务扫同一范围 → 大量 Gap 锁堆积 -
INSERT SELECT中源表扫描没走索引 → 隐式加 Next-Key 锁,等效于锁表
怎么快速定位是哪个 Gap 锁在拦路?
别查 INSERT 本身,重点查当前活跃事务持有的间隙类锁:
- 先看谁没提交:
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING' - 再查锁详情(MySQL 8.0+):
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') - 关键比对:你要插
id = 105,若某行LOCK_DATA是100, 110且LOCK_MODE含GAP,就是它 -
LOCK_DATA为NULL或空值 → 很可能是全表扫描导致的隐式间隙锁,说明缺索引
绕过阻塞的实操要点
核心思路是让 InnoDB 尽量只加记录锁(REC_NOT_GAP),少扫间隙:
- 给常用查询条件建联合索引(如
(status, create_time)),比单列status索引锁得更准 - 避免在非唯一索引字段上范围查询后立刻 INSERT;可拆成两步:
SELECT id FROM t WHERE status = 'pending' LIMIT 1→ 再INSERT ... WHERE id = ? - 若业务允许,临时降级隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED(注意验证binlog_format = ROW) -
ON DUPLICATE KEY UPDATE和INSERT IGNORE同样要走唯一性校验并加锁,一样中招,不能靠它们绕过
真正难处理的点在于:Gap 锁不可见、插入意向锁不显式暴露,你看到的往往只是「INSERT 卡住」或「Deadlock found」,但根源藏在几行之前某个看似无害的 SELECT ... FOR UPDATE 里。










