InnoDB中S锁与X锁严格互斥,因语义根本冲突而被设计禁止共存:S锁仅允读、X锁必须写,任一事务持S锁时其他事务的X锁申请(如UPDATE、SELECT FOR UPDATE)均被阻塞,反之亦然,这是保障隔离性的底线机制。

因为S锁只允许读、X锁必须写,两者语义根本冲突——InnoDB直接禁止共存,不是“可能出错”,而是“设计上就不让发生”。
SELECT ... FOR SHARE 和 UPDATE 为什么一定互斥
当你执行 SELECT * FROM user WHERE id = 1 FOR SHARE,InnoDB会在该行加S锁;此时另一个事务执行 UPDATE user SET name='x' WHERE id = 1,会尝试加X锁。但S锁和X锁在兼容性矩阵里明确标记为 ❌ 拒绝,所以后者立刻被阻塞,直到前一个事务提交或回滚。
这不是性能优化问题,是隔离性底线:如果允许S+X共存,事务A刚读完余额,事务B就扣款提交,A再基于旧值做判断(比如“余额够就发券”),就会导致超发——这就是脏读或更新丢失的源头。
- S锁存在时,任何X锁申请(包括
SELECT ... FOR UPDATE、DELETE、UPDATE)都会被挂起 - 反过来,X锁持有期间,所有S锁申请(
FOR SHARE或LOCK IN SHARE MODE)也一律等待 - 注意:普通
SELECT(无锁提示)在RC/RR隔离级别下走MVCC快照读,不加S锁,所以不会被阻塞
同一个事务里重复加S锁不报错,但跨事务就不行
事务A执行两次 SELECT ... FOR SHARE,第二次不会触发锁兼容检查——InnoDB把锁按事务归组,复用已有S锁。但事务B想在同一行加S锁,可以;想加X锁?直接排队。
这种“自己人不拦、外人严查”的逻辑,容易让人误以为“S锁很松”,其实它只是对同事务友好,对外依然守着互斥红线。
- 锁的持有者是事务,不是SQL语句
- 事务未结束前,S锁一直有效,哪怕中间执行了其他非锁操作
- 如果你看到两个事务都卡在
FOR SHARE上,大概率是其中一方其实持有了X锁(比如前面有未提交的UPDATE)
真正拦路的常常不是S/X本身,而是背后的意向锁
排查时最常掉坑的地方:你看到 Waiting for table level lock,第一反应是“谁锁了整张表?”,但实际可能是事务A执行了 SELECT ... FOR UPDATE,InnoDB自动在表上加了 IX(意向排他锁);此时事务B想执行 LOCK TABLES t READ(需要表级S锁),而 IX 和表级S锁冲突,于是报错——但日志里根本不提 IX。
- 意向锁(
IS/IX)是隐式加的,不显现在SHOW ENGINE INNODB STATUS的 LOCK WAIT 部分,得看 TRANSACTIONS 段里的 lock_mode - 表级操作(如
ALTER TABLE、FLUSH TABLES WITH READ LOCK)容易和IX冲突,尤其在高并发写场景 - 别只盯着行锁语句,先用
SELECT * FROM performance_schema.data_locks看当前所有锁的实际类型和粒度
真正难的不是记住S/X互斥,而是当阻塞发生时,能快速区分:是行级S/X直接打架,还是意向锁在表级悄悄拦路。后者更隐蔽,也更容易被忽略。


















