MySQL锁系列|插入意向锁完整详解
前置阅读:Gap间隙锁、Next‑Key Lock临键锁
一、什么是插入意向锁 Insert Intention Lock
插入意向锁是 InnoDB 在执行 INSERT 插入前自动施加的一种特殊的间隙锁(Gap Lock)。
⚠️注意:名字带"意向",它不是表级的 IS/IX意向锁 ,属于行锁体系,作用于索引间隙。
生效前提:InnoDB、RR可重复读隔离级别;RC隔离级别关闭Gap锁,也就不存在插入意向锁。
作用:标记当前事务打算在某个索引间隙插入新数据。
- 多个事务向同一个间隙插入不同索引值时,插入意向锁互相兼容,可以并发插入,不会互相阻塞,提升插入并发性能。
- 如果该间隙已经被其他事务持有 Gap间隙锁 / Next‑Key临键锁,插入意向锁会被阻塞,必须等待间隙锁释放,以此保护幻读不发生。
二、触发时机
执行INSERT语句时,InnoDB内部流程:
- 定位待插入记录在B+树索引中的间隙位置
- 尝试获取该间隙的插入意向锁
- 如果间隙被Gap/Next‑Key锁占用,INSERT阻塞等待;间隙空闲则获取成功
- 成功之后,对真正插入的数据行,加Record X排他行锁
- 事务提交,锁全部释放
普通快照读
SELECT不会产生;UPDATE / DELETE / SELECT ... FOR UPDATE也不会产生插入意向锁,只有INSERT才会生成。
三、锁兼容性矩阵
| 锁类型 | 插入意向锁 | Gap间隙锁 / Next‑Key临键锁 | Record S/X行锁 |
|---|---|---|---|
| 插入意向锁 | ✅兼容 | ❌互斥阻塞 | ✅兼容 |
重点理解两点:
- 插入意向锁与插入意向锁互相兼容:多个事务可以同时拿到同一个间隙的插入意向锁,只要插入主键/索引值不一样,可以并发插入成功,不会阻塞。
- 插入意向锁 和 Gap锁、Next‑Key临键锁互斥:只要间隙被别人加了间隙锁,所有INSERT往这个间隙插入全部阻塞等待,这是RR防止幻读的核心机制。
示例:索引已有 1,5,间隙 (1,5)
- 事务A:插入 id=2
- 事务B:插入 id=3
两个事务同时获取间隙(1,5)的插入意向锁,互不阻塞,可以同时完成插入。
如果事务C先执行select ... for update,给间隙(1,5)加上Gap锁;那么A、B的INSERT全部阻塞。
四、实战演示
测试表
sql
CREATE TABLE t_lock(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32)
);
INSERT INTO t_lock(name) VALUES('A'),('E');
-- id:1,5;间隙 (1,5)
场景1:间隙持有临键锁,INSERT被阻塞
会话A
sql
BEGIN;
-- id=2不存在,唯一索引等值查询查无数据,退化为Gap锁,锁住间隙 (1,5)
SELECT * FROM t_lock WHERE id=2 FOR UPDATE;
此时间隙(1,5)被加上Gap间隙锁。
会话B
sql
BEGIN;
-- 尝试插入id=3,需要获取间隙(1,5)插入意向锁,和Gap锁冲突,直接阻塞
INSERT INTO t_lock(id,name) VALUES(3,'C');
查看锁信息:
sql
SELECT ENGINE_TRANSACTION_ID,LOCK_TYPE,LOCK_MODE,LOCK_TABLE,LOCK_DATA
FROM performance_schema.data_locks;
- 会话A锁:
LOCK_MODE: X,GAPGap间隙锁 - 会话B锁:
LOCK_MODE: X,INSERT_INTENTION插入意向锁,状态为WAITING等待中
会话A执行COMMIT;释放Gap锁,会话B立刻完成插入。
场景2:同一间隙,多个INSERT并发插入互不阻塞
会话A:
sql
BEGIN;
INSERT INTO t_lock(id,name) VALUES(2,'B');
会话B:
sql
BEGIN;
INSERT INTO t_lock(id,name) VALUES(3,'C');
A、B都获取间隙(1,5)插入意向锁,互相兼容,不会阻塞,两个insert都正常执行。
只有插入完全相同唯一键值时,才会报唯一键冲突报错,这是约束校验,不是锁阻塞。
五、RC隔离级别下没有插入意向锁
RC读已提交隔离级别:
- Gap间隙锁、Next‑Key临键锁全部关闭
- 插入意向锁也随之消失
- INSERT只会对插入行加Record X记录锁,间隙没有任何锁保护
- 并发插入性能更高,但当前读会出现幻读现象。
很多生产环境把隔离级别改为RC,就是为了消除Gap锁+插入意向锁带来的大量INSERT阻塞问题。
六、线上典型坑点与死锁案例
坑1:看不到insert语句,但是大量insert阻塞等待
现象:业务大量INSERT慢、堆积,但是没有慢SQL,也没有update/delete长事务。
根因:某个长事务执行for update / update / delete产生Gap锁/Next‑Key锁,锁住索引间隙,所有落在该间隙的INSERT全部等待插入意向锁释放。
✅排查:查询performance_schema.data_locks看是否存在X,GAP间隙锁,找到持有Gap锁的长事务。
坑2:插入意向锁参与死锁(经典死锁场景)
复现流程:
表数据 id:1, 10
会话A:
sql
BEGIN;
SELECT * FROM t_lock WHERE id=5 FOR UPDATE; -- 查无数据,加Gap锁 (1,10)
会话B:
sql
BEGIN;
INSERT INTO t_lock(id,name) VALUES(3,'B'); -- 获取插入意向锁,等待A释放Gap锁
会话A:
sql
INSERT INTO t_lock(id,name) VALUES(4,'A'); -- A现在尝试插入4,也需要获取本间隙插入意向锁,等待B
循环等待:B等A的Gap锁;A等B的插入意向锁 → 触发死锁,InnoDB自动回滚其中一个事务。
关键点:同一个事务,自己持有某个间隙Gap锁,自己也不能往这个间隙做INSERT,同样会等待,产生死锁。
坑3:误以为INSERT不会加锁
INSERT不是完全无锁:RR下INSERT会加插入意向锁,同时对新行加X记录锁;长事务不commit,插入的行X锁也会持续持有。
七、常见误区澄清
❌误区1:插入意向锁是表级IS/IX意向锁
完全两码事。IS/IX是表锁层面;插入意向锁属于行锁‑间隙锁体系。
❌误区2:插入意向锁会阻塞其他INSERT
插入意向锁之间互相兼容;只有遇上Gap锁、临键锁才阻塞。
❌误区3:RC隔离级别也会产生插入意向锁
RC关闭Gap锁机制,不会产生插入意向锁。
❌误区4:INSERT一定会产生插入意向锁
INSERT需要定位到索引间隙才会生成;RC模式无;如果索引不存在间隙,也不会触发。
❌误区5:同一个事务持有Gap锁,可以直接往该间隙INSERT
不行,自己持Gap锁,自己INSERT同样需要获取插入意向锁,会等待,极易死锁。
八、总结
- Insert Intention Lock插入意向锁,是RR级别下INSERT产生的特殊间隙锁,标记事务有插入意图,RC隔离级别不存在。
- 插入意向锁之间互相兼容,支持多事务同一间隙并发插入不同索引值,提升插入性能;但与Gap锁、Next‑Key临键锁互斥。
- 核心职责:兼顾并发插入性能,同时配合Gap锁阻止幻读;间隙一旦被Gap锁锁住,所有INSERT全部阻塞。
- 线上故障特征:大量INSERT阻塞等待,根源往往是长事务产生了Gap/Next‑Key锁;同一个事务持有Gap锁又执行INSERT会触发死锁。
- 优化方案:优先使用RC隔离级别消除间隙锁体系;避免长事务执行当前读(for update/update/delete)缩小锁范围;加锁SQL尽量走唯一索引,触发锁退化减少Gap锁产生。
MySQL锁系列全部完结:全局锁、MDL元数据锁、S表读锁、X表写锁、IS意向共享锁、IX意向排他锁、Record S/X行锁、Gap间隙锁、Next‑Key临键锁、Insert Intention插入意向锁。