意向锁是表级加锁前的快速判断机制,通过一次元数据检查替代全表行锁扫描,避免大表随机i/o;is与ix彼此兼容,但均与表级x锁互斥;其自动加锁特性易导致ddl被阻塞,需在rr隔离级下验证。

为什么表级加锁要先查意向锁,而不是直接扫行锁?
因为逐行检查是否已被加锁(比如判断全表有没有 X 锁)在大表上会触发大量随机 I/O,性能崩得比锁等待还快。InnoDB 用意向锁把“表里有没有行被锁”这个判断压缩成一次表级元数据检查——只要看到 IX 锁,就知道底下至少有一行正被修改;看到 IS 锁,就知道有行正被读;两者都无,才可能安全加表级 X 锁。
- 不走意向锁路径的代价:对百万行表加
LOCK TABLES t WRITE,引擎真会尝试遍历所有聚簇索引页找活跃行锁(实际不会真干,但兼容逻辑上必须防住) - 意向锁是自动加的:你执行
UPDATE t SET a=1 WHERE id=5,InnoDB 自动在表上加IX、在行上加X,无需手动干预 - 显式加表锁时,意向锁才是第一道闸机:比如
SELECT * FROM t LOCK IN SHARE MODE会加IS,此时另一个事务想LOCK TABLES t WRITE就立刻被拒,根本不用碰任何数据页
IS 和 IX 锁之间到底“兼容”还是“冲突”?
IS 和 IX 彼此兼容,这是关键设计。否则两个只读事务(都加 IS)或两个更新事务(都加 IX)就互相阻塞了,彻底废掉并发能力。
- 真正冲突的是:表级
X锁与任何意向锁(IS/IX)都互斥——因为表级写锁要求“整张表完全空闲”,不能容忍底下有任何行锁 -
S(行级共享锁)和IX兼容:允许读已更新的行(如SELECT ... LOCK IN SHARE MODE与UPDATE并存) -
X(行级排他锁)和IS兼容:允许更新已被读的行(如UPDATE与SELECT ... LOCK IN SHARE MODE并存)
什么时候会意外触发 IX 锁阻塞表级 DDL?
不是只有 UPDATE 才加 IX。任何对行施加 X 锁的操作都会连带加 IX,包括 INSERT、DELETE、显式 SELECT ... FOR UPDATE,甚至某些唯一键冲突回滚场景也会短暂持有 IX。
- 典型卡死链:
TRX-A正在执行INSERT INTO t VALUES (100)(未提交),此时ALTER TABLE t ADD COLUMN c INT会被阻塞——DDL 需要表级X锁,而IX与之冲突 - 排查方法:查
information_schema.INNODB_TRX看TRX_STATE和TRX_QUERY,再关联INNODB_LOCKS和INNODB_LOCK_WAITS确认是否被IX挡住 - 规避建议:DDL 尽量避开业务高峰;用
pt-online-schema-change替代原生ALTER,它会绕过表级X锁需求
测试 IS/IX 行为时最容易忽略的隔离级别陷阱
在 READ COMMITTED 或更低隔离级别下,SELECT 不加任何锁(包括 IS),所以你测不出 IS 的效果。必须用 REPEATABLE READ 或显式加锁语句才能触发意向锁。
- 错误示范:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT * FROM t;→ 表上无IS,后续LOCK TABLES t WRITE不会等 - 正确验证:
START TRANSACTION; SELECT * FROM t LOCK IN SHARE MODE;→ 此时查INNODB_TRX能看到TRX_ISOLATION_LEVEL = REPEATABLE READ且表上有IS - 注意:MySQL 8.0+ 中
information_schema.INNODB_LOCKS已废弃,改用performance_schema.data_locks查当前锁状态
意向锁本身不解决死锁,也不提升单条 SQL 速度,它只是让“表级锁能否成立”这个决策从 O(n) 降到 O(1)。真正容易翻车的地方,永远是那个你以为没锁的 SELECT,其实悄悄带上了 IS,然后默默挡住了凌晨三点的 DDL 任务。










