插入意向锁是表明“将向某间隙插入记录”的特殊间隙锁,本身不互斥,真正阻塞INSERT的是它与gap lock或next-key lock的冲突;常见于RR隔离级别下高并发插入同一索引间隙场景。

插入意向锁是什么,它真在阻塞你的 INSERT 吗?
插入意向锁(INSERT intention lock)不是真正的“锁记录”,而是一种特殊的间隙锁(GAP lock),用来表明“我打算往这个间隙里插一条记录”。它本身不互斥——多个事务可以同时持有同一间隙的插入意向锁。真正阻塞你的,往往是它和别的锁(比如另一个事务持有的 gap lock 或 next-key lock)冲突。
常见错误现象:SHOW ENGINE INNODB STATUS 里看到 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: INSERT intention lock...,但查不到谁在 hold 锁——大概率是别的事务正拿着覆盖该间隙的 gap lock(比如执行了 SELECT ... FOR UPDATE 或唯一键冲突回滚残留的锁)。
- 使用场景:高并发向同一个索引间隙插入(如按时间戳递增写入、批量导入无序 ID)
- 注意:只有在开启
innodb_locks_unsafe_for_binlog=OFF(默认)且事务隔离级别 ≥REPEATABLE READ时才启用插入意向锁 - 性能影响:它本身开销极小,但会触发锁冲突检测;若频繁等待,说明上游有长事务或未提交的范围锁操作
怎么快速定位谁在挡你的 INSERT?
别只盯着 INSERT 语句,重点看谁在持有可能重叠的 gap lock。最直接的方法是抓当前锁状态:
SELECT * FROM information_schema.INNODB_TRX\G
再结合:
SELECT * FROM information_schema.INNODB_LOCKS\G
(MySQL 8.0+ 改用 performance_schema.data_locks)
- 关键字段看:
LOCK_TRX_ID对应事务 ID,LOCK_MODE是GAP还是NEXT-KEY,LOCK_DATA显示锁住的索引值范围 - 如果发现某个事务的
LOCK_MODE = GAP且LOCK_DATA覆盖了你要插入的位置(比如你要插id=105,它锁了(100, 110)),那就是它 - 容易踩的坑:
SELECT ... FOR UPDATE在非唯一条件上执行后没提交,会持续持有 gap 锁;或者唯一键冲突后事务没回滚干净,InnoDB 可能残留意向锁等待链
INSERT 并发冲突的三种典型模式与应对
不是所有“插不进”都怪插入意向锁。先分清模式,再下手:
-
唯一键冲突导致的隐式锁升级:两个事务同时 INSERT 相同唯一值,后一个会被阻塞在
duplicate key检查阶段,表现为等前一个事务释放record lock。解决:业务层幂等控制,或改用INSERT ... ON DUPLICATE KEY UPDATE -
主键/索引间隙被前置事务锁死:事务 A 执行
SELECT id FROM t WHERE id BETWEEN 100 AND 200 FOR UPDATE,事务 B 插入id=150就会等。解决:避免在写密集场景用大范围FOR UPDATE;或改用READ COMMITTED隔离级别(此时不加 gap lock) -
自增锁(
auto-inc lock)争用:大批量INSERT ... SELECT或REPLACE触发表级自增锁,阻塞其他 INSERT。解决:拆成小批量;或启用innodb_autoinc_lock_mode=2(交错模式,需 binlog_format=ROW)
监控和预防:别等堵了才看锁
线上不敢随便跑 INNODB_STATUS?那就靠指标驱动:
- 监控
Innodb_row_lock_waits和Innodb_row_lock_time_avg:突增说明锁等待变多,但无法区分是插入意向锁还是别的 - 定期查
SELECT * FROM performance_schema.events_statements_current WHERE SQL_TEXT LIKE '%INSERT%' AND TIMER_WAIT > 1000000000(超 1s 的 INSERT) - 最容易被忽略的一点:插入意向锁不会出现在
sys.innodb_lock_waits视图里——它只展示“已持有”和“正在等”的锁对,而插入意向锁的冲突往往发生在加锁路径中,得靠data_locks+data_lock_waits联查才能看清全链路


















