插入意向锁是间隙锁变体,用于INSERT前探测gap安全性,允许多事务并发插入同一gap不同位置,不互斥但会与gap上的X锁/Next-Key锁冲突,且在唯一索引重复插入时易引发死锁。

插入意向锁不是用来“允许多个事务并发插入同一间隙”的工具,而是为了在不互相阻塞的前提下,让多个事务能各自判断自己插入位置是否安全——它本身不允许多个事务插入同一行,只允许插入同一间隙中不同位置的记录。
插入意向锁本质是间隙锁的一种变体
它由 INSERT 语句在真正写入前自动加,作用范围是「索引中两个相邻记录之间的空隙(gap)」,比如现有主键值为 10 和 20,那么 (10, 20) 就是一个 gap。插入意向锁就落在这个区间上,而不是某一行。
关键点:
- 它不和别的插入意向锁冲突:事务 A 插入
15、事务 B 插入18,两者都对(10, 20)加插入意向锁,但互不阻塞 - 它会和 gap 上已有的
X锁或Next-Key Lock冲突:如果事务 C 已经对(10, 20)加了间隙锁(比如执行了SELECT ... FOR UPDATE WHERE id BETWEEN 12 AND 17),那 A 和 B 都会被阻塞 - 它不解决唯一键冲突:如果两个事务同时插入相同
id_card(有唯一索引),其中一个会拿到该记录上的S锁做 duplicate key 检查,另一个被X锁阻塞,极易引发死锁
为什么 INSERT 不直接加记录锁,而要先加插入意向锁
因为 InnoDB 必须在插入前确认「这个位置现在没被别人锁住,且将来也不会因回滚产生幻读」。插入意向锁就是这个「探路」动作。
典型流程(以非唯一索引插入为例):
- 事务扫描到目标 gap,比如
(10, 20) - 在该 gap 上加插入意向锁(
Insert Intention Lock) - 检查是否有其他事务对该 gap 加了
Gap Lock或Next-Key Lock;有则等待 - 检查插入位置是否已有相同唯一键值(如
uni_idx_id_card),有则尝试加S锁并等待 - 通过所有检查后,才在新记录上加
X记录锁,并提交
容易被忽略的死锁高发场景
插入意向锁本身不冲突,但它和唯一索引上的锁协作时非常脆弱。最常见死锁链是:
- 事务 A 执行
INSERT INTO sys_user (id_card) VALUES ('110')→ 在uni_idx_id_card上加插入意向锁,发现无重复,继续 - 事务 B 同样执行
INSERT INTO sys_user (id_card) VALUES ('110')→ 在同一唯一值上尝试加S锁做校验,被 A 的插入意向锁+后续 X 锁阻塞,开始等待 - 此时事务 A 又执行
UPDATE sys_user SET phone = '130' WHERE id_card = '110'→ 需要对该记录加X锁,但 B 正持有S锁(用于 duplicate check)→ A 等待 B - A 等 B,B 等 A → 死锁
这种环路不会出现在普通非唯一字段插入中,但只要表上有唯一索引,且并发插入相同值,就极可能触发。
如何验证当前是否存在插入意向锁
MySQL 8.0+ 可通过 performance_schema.data_locks 查看,注意过滤 lock_mode 字段:
SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_MODE = 'INSERT_INTENTION';
返回结果中 LOCK_DATA 显示的是 gap 范围,比如 10, 20 表示该锁覆盖 (10, 20) 区间;若为 NULL,说明是表级意向锁(如 IX),不是插入意向锁。
真正的难点不在理解“它是什么”,而在于意识到:插入意向锁只是并发插入的“入场券”,但唯一约束、索引结构、事务执行顺序,才是决定最终能否成功插入的关键变量——漏掉其中任一环,都会让看似安全的并发插入变成死锁温床。


















