插入意向锁仅在repeatable read隔离级别下生效;read committed下因禁用间隙锁而无需该锁,此时无阻塞但存在幻读风险,可通过performance_schema.data_locks确认其是否存在。

插入意向锁只在 RR 隔离级别下起作用
MySQL 的 INSERT 语句是否加插入意向锁,取决于事务隔离级别。它**只在 REPEATABLE READ 下生效**;在 READ COMMITTED 下,InnoDB 不用间隙锁(Gap Lock),自然也就不需要插入意向锁来协调——此时插入基本不阻塞,但幻读风险上升。
-
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ是默认值,但线上环境可能被显式改过,务必用SELECT @@transaction_isolation确认 - 如果误设为
READ COMMITTED,你观察performance_schema.data_locks就看不到LOCK_MODE = 'INSERT_INTENTION' - DDL 操作(如加索引)或某些 ORM 自动降级隔离级别时,会悄悄绕过插入意向锁机制
怎么确认插入意向锁正在生效
别猜,直接查锁表。MySQL 8.0+ 推荐走 performance_schema.data_locks,比 SHOW ENGINE INNODB STATUS 更精准、可过滤。
- 先复现阻塞:会话 A 执行
SELECT * FROM t WHERE id > 15 AND id (锁住间隙),会话 B 执行 <code>INSERT INTO t VALUES (20)(被卡住) - 新开会话 C,执行:
SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE OBJECT_NAME = 't';
- 找到
LOCK_MODE为'INSERT_INTENTION'的行,LOCK_DATA显示间隙范围(如10, 30),说明它正“声明”自己要插进这个空档
为什么两个 INSERT 不互相阻塞,却会被 SELECT ... FOR UPDATE 卡住
插入意向锁本质是「低冲突」的间隙锁,它**不排斥其他插入意向锁,但排斥 Gap Lock 和 Next-Key Lock**。
- 事务 A 插
20、事务 B 插25→ 各自申请 (10,30) 上的插入意向锁 → 兼容,不阻塞 - 但事务 C 执行
SELECT ... WHERE id BETWEEN 15 AND 25 FOR UPDATE→ 在 (10,30) 上加 X Gap Lock → 与插入意向锁冲突 → A/B 都得等 C 提交 - 注意:插入意向锁和记录锁(X, REC_NOT_GAP)也不冲突,所以插入完成后加的行锁不会影响已存在的 Gap Lock
容易忽略的坑:唯一索引冲突会跳过插入意向锁
插入意向锁只在「准备插入但记录尚不存在」时申请。一旦碰到唯一键冲突(比如重复主键或唯一索引值),InnoDB 会立刻尝试加记录锁(X Lock)去判断是否存在——这时已经不走插入意向锁路径了。
- 表有唯一索引
uk_name,事务 A 插('alice')未提交;事务 B 再插('alice')→ 直接等待 A 的记录锁释放,不是等插入意向锁 - 这种场景下,
data_locks中看到的是LOCK_MODE = 'X,REC_NOT_GAP',不是INSERT_INTENTION - 所以“并发插入不阻塞”的前提,是插入的值在索引上互不重叠;否则就退化成普通行锁等待
实际调试时,最常翻车的地方不是锁类型记错,而是忘了查隔离级别、没清空缓存事务、或者把唯一约束冲突当成插入意向锁阻塞来看——结果对着错误的方向调半天。










