意向锁(IS/IX)本身是表级“意图声明”,相互兼容不冲突;真正导致阻塞的是其后申请的行级S/X锁或间隙锁,尤其在无索引导致全表扫描时,大量行锁堆积引发等待。

意向锁本身不冲突,冲突的是它背后的行锁请求
MySQL 的 IS(意向共享)和 IX(意向排他)锁是表级锁,作用只是“声明意图”,它们之间几乎不会直接冲突。真正卡住你的,是事务在获取意向锁后,紧接着尝试加的行级 S 锁或 X 锁——而这些锁被其他事务占着,或者被间隙锁挡住了。
比如两个事务同时执行 SELECT ... FOR UPDATE WHERE status = 'pending',如果 status 没索引,InnoDB 会:先加 IX 锁(快),再全表扫描并逐行尝试加 X 锁(慢且易排队)。你看到的“意向锁等待”,其实是日志里简化了描述,真实阻塞点在行锁层。
为什么 EXPLAIN 显示 type=ALL 就等于意向锁风险拉满
当 EXPLAIN 对一条带 FOR UPDATE 或 UPDATE 的语句返回 type: ALL 或 type: index,说明 InnoDB 必须遍历大量行才能定位目标。每扫一行,就可能触发一次行锁申请;哪怕最终只改 1 行,中间扫过的成百上千行都可能被加上临时 X 锁或间隙锁——而这些锁都会让后续事务在申请 IX 后卡在行锁环节。
- 隐式转换会让索引失效:比如
WHERE user_id = '123'(user_id是INT)→ 实际走全表 - 函数包裹字段:比如
WHERE DATE(create_time) = '2026-06-01'→ 索引失效,全扫 - 联合索引没用最左前缀:建了
(status, category),但只查WHERE category = 'A'→ 全表扫
READ COMMITTED 能缓解,但不是万能解药
在 REPEATABLE READ 下,InnoDB 用 Next-Key Lock(记录锁 + 间隙锁)防止幻读,这会让 INSERT、UPDATE 的锁范围远超实际修改行。换成 READ COMMITTED 后,间隙锁被禁用,只加纯记录锁,且不匹配的锁会立刻释放——这对避免“扫到但没改”的锁堆积很有效。
但注意:
- 业务必须能接受“不可重复读”:比如订单页刷新两次看到不同状态
- 唯一键冲突仍可能死锁:三个事务插同一
id,即使 RC 级别,插入意向锁 + 唯一键检查仍可能循环等待 - 不能替代索引优化:没索引的
WHERE在 RC 下照样全表扫、照样加一堆行锁
真正该盯住的不是意向锁,而是 trx_state 和 blocking_trx_id
查锁冲突,别只盯着“意向锁”字眼。直接看:
SELECT trx_id, trx_state, trx_started, trx_wait_started,
trx_mysql_thread_id, trx_query
FROM information_schema.INNODB_TRX
WHERE trx_state = 'LOCK WAIT';
再关联 INNODB_LOCK_WAITS 找出 blocking_trx_id,顺藤摸瓜看那个阻塞事务在干啥。大概率会发现:
- 一个事务在执行
UPDATE却没走索引,已持有几百行X锁 - 另一个事务正卡在同一批 ID 上做
SELECT FOR UPDATE - 第三个事务在事务里调了 HTTP 接口,10 秒没提交,锁一直挂着
意向锁只是表级“门禁牌”,真正堵住门口的,永远是里面那些没及时出门、还乱占座的人。


















