Insert Intention Lock 是一种标记锁,不直接阻塞插入,仅表明“将向某间隙插入记录”,其阻塞源于与 gap lock 或 next-key lock 的冲突;仅在可重复读隔离级别下生效。

Insert Intention Lock 是什么,它真能“锁住插入”吗?
不是。Insert Intention Lock 本身不阻塞任何插入,它只是个**标记**:告诉其他事务“我正打算往这个间隙里插一条记录”。真正的阻塞来自它和 gap lock 或 next-key lock 的冲突。
常见错误现象:INSERT 卡住、事务长时间等待、SHOW ENGINE INNODB STATUS 里看到 insert intention waiting for x lock —— 这说明有别的事务持有了覆盖同一间隙的 gap lock(比如一个未提交的 SELECT ... FOR UPDATE 或 DELETE)。
- 它只在可重复读(
REPEATABLE READ)隔离级别下生效;读已提交(READ COMMITTED)下不使用 gap lock,也就没有 insert intention lock 的冲突场景 - 它不和行锁(
record lock)冲突:两个事务往同一索引位置插不同主键值,只要不落在同一间隙,互不干扰 - 它必须配合索引使用:全表扫描或无可用索引时,InnoDB 可能升级为锁整张表(
table lock),行为不可控
怎么复现 insert intention lock 等待?
关键在于制造一个被 gap lock 锁住的空隙,再让另一个事务尝试往里面插。
实操步骤:
- 建表:
CREATE TABLE t (id INT PRIMARY KEY, v INT, INDEX idx_v(v)); - 插入数据:
INSERT INTO t VALUES (1,10), (5,50); - 事务 A 执行:
SELECT * FROM t WHERE v BETWEEN 15 AND 45 FOR UPDATE;→ 锁住间隙 (10,50),即 v ∈ (15,45) 对应的索引间隙 - 事务 B 执行:
INSERT INTO t VALUES (2, 25);→ 卡住!因为 v=25 落在被锁间隙内,需获取insert intention lock,但被事务 A 的 gap lock 拒绝
注意:v=25 本身不对应现有记录,但它的索引位置在事务 A 的锁范围内 —— 这就是 insert intention lock 生效的典型边界。
为什么加了唯一索引,insert intention lock 还会等?
唯一索引(UNIQUE)下,InnoDB 对重复值检测会先加 insert intention lock,再加 record lock;但如果唯一冲突检查过程中发现已有记录被其他事务用 SELECT ... FOR UPDATE 锁住,就会等那个 record lock 释放。
更隐蔽的情况是:唯一约束校验依赖二级索引,而该索引上存在未提交的 gap lock(比如 UPDATE 修改了索引字段但未提交),此时新插入即使不违反唯一性,也可能因间隙被锁而等待。
- 唯一索引上的 insert intention lock 不排斥其他 insert intention lock,只排斥 gap/next-key lock
-
INSERT IGNORE和ON DUPLICATE KEY UPDATE同样会申请 insert intention lock,失败时才回退,不是“跳过锁” - 如果唯一键是
NULL允许多行(如非严格模式或非主键唯一索引),NULL值不参与 gap lock,但 insert intention lock 仍会在 NULL 区间申请,容易误判
如何快速定位 insert intention lock 导致的阻塞?
看 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCK_WAITS 太绕,直接用 SHOW ENGINE INNODB STATUS\G 更快。
重点关注两块:
- “TRANSACTIONS” 部分找状态为
LOCK WAIT的事务,记下trx_id - “LATEST DETECTED DEADLOCK” 下方的 “WAITING FOR THIS LOCK TO BE GRANTED” 行,通常含
insert intention lock on … waiting for … x lock - 往上翻,找到持有对应 x lock 的事务(
HOLDS THE LOCK(S)),看它执行的语句和锁类型(大概率是gap lock或next-key lock)
容易被忽略的一点:锁等待可能发生在二级索引上,而非主键。比如你查 v=25 等待,但实际被锁的是 idx_v 索引的间隙 —— 此时 SELECT ... FOR UPDATE 并没显式用到 v 字段,只是碰巧扫到了那个索引范围。










