s锁与x锁互斥,s锁之间兼容:同一行上多个事务可同时持s锁并发读,但任一s锁存在时x锁申请必阻塞,反之亦然,根本原因是读写语义冲突,需防止脏读或更新丢失。

MySQL 中 S 锁(共享锁)和 X 锁(排他锁)的互斥关系,核心就落在一张两行两列的兼容性矩阵上。它不靠猜测,而是由 InnoDB 严格按规则检查——只要冲突,请求锁的语句就会被挂起等待,直到已有锁释放。
S 锁与 X 锁的兼容性矩阵怎么读
这张表描述的是:当某一行上已存在一种锁时,另一个事务尝试加另一种锁,是否允许成功。
| 已持有锁 \ 请求锁 | S 锁 | X 锁 |
|---|---|---|
| S 锁 | ✅ 允许 | ❌ 阻塞 |
| X 锁 | ❌ 阻塞 | ❌ 阻塞 |
- ✅ 表示“可以同时存在”,即不互斥;
- ❌ 表示“不能共存”,即互斥,后到的锁请求必须等待。
这个判断是行级粒度、事务级生效的:只看同一行记录上有没有锁冲突,跟其他行、其他表无关。
为什么 S 和 X 一定互斥
根本原因是读写语义冲突:
- S 锁只允许读,保证数据不被修改;
- X 锁要写,必须独占该行,防止其他事务读到中间态或覆盖更新。
如果允许 S+X 并存,就会出现:
- 事务 A 拿 S 锁读取余额为 100;
- 事务 B 同时拿 X 锁扣减 50,写入 50;
- 事务 A 基于旧值做后续逻辑(比如校验后发通知),就产生了脏读或逻辑错乱。
所以 InnoDB 直接禁止这种组合,任何一方持锁,另一方申请都会被阻塞。
S 锁之间为什么能共存
多个事务同时读同一行,不改变数据,也不影响彼此一致性。例如:
- 事务 A 执行
SELECT * FROM order WHERE id = 123 LOCK IN SHARE MODE; - 事务 B 几乎同时执行同样语句;
- 两者都拿到 S 锁,可并发读,互不干扰。
这是提升高并发读性能的基础设计。注意:这个“兼容”仅针对同一行;不同行上的 S 锁压根不在一个锁冲突域里,无需判断兼容性。
同一个事务内重复加锁为什么不出问题
锁归属以事务为单位,不是每次 SQL 单独算:
- 事务 A 第一次加 S 锁,InnoDB 记录 “事务A → 持有 id=1 的 S 锁”;
- 第二次再对同一行加 S 锁,InnoDB 发现实为自身持有,直接复用,跳过兼容性检查;
- 但事务 B 此时来加 X 锁,就会触发检查,发现已有 S 锁 → ❌ 阻塞。
这点常被误认为“锁没生效”,其实是机制使然:锁管理是事务级的,不是语句级的。
实际阻塞中容易忽略的间接原因
表面上是 S/X 冲突,但很多“等锁”其实卡在意向锁(IX/IS)上:
- 事务 A 执行
SELECT * FROM t FOR UPDATE,先在表上加 IX 锁,再在行上加 X 锁; - 此时事务 B 想执行
LOCK TABLES t READ(需要表级 S 锁),会被拒绝——因为 IX 与表级 S 锁冲突; - 日志可能只显示 “Waiting for table level lock”,完全不提 IX,让人误以为是 SQL 写法问题。
排查时得结合 SHOW ENGINE INNODB STATUS 看 lock wait 链,不能只盯住当前 SQL 的锁类型。











