S锁与X锁互斥,只要作用于同一行索引记录就必然冲突;S+S兼容,X与任何锁都不兼容,冲突与加锁顺序、显隐式无关,是InnoDB底层硬规则。

怎么一眼看出 S 锁和 X 锁会冲突?
只要记住一条铁律:S 锁与 X 锁互斥,不管谁先谁后、谁显式谁隐式、是否同一事务——只要它们作用在**同一行索引记录**上,就必然冲突。这不是配置问题,是 InnoDB 的底层锁兼容性规则。
常见误判点:看到两个 SELECT ... LOCK IN SHARE MODE 同时执行成功,就以为“S 锁之间能共存,那加个 X 锁应该也行”。错。S+S 成功,恰恰是因为它们彼此不冲突;但只要其中一行已有 S 锁,任何事务再申请 X 锁(哪怕只是 UPDATE ... WHERE id = 1),就会立刻被挂起等待。
- 冲突只发生在“同一索引记录”上:事务 A 对
id=5加了 S 锁,事务 B 对id=6加 X 锁,完全不冲突 - 普通
SELECT不加锁,SELECT ... FOR UPDATE默认加 X 锁,SELECT ... LOCK IN SHARE MODE默认加 S 锁 - UPDATE/DELETE 语句即使 WHERE 条件没匹配到数据,只要扫描到了该行(比如走全表扫描),也会尝试加 X 锁——可能触发锁等待
为什么有时候没写 FOR UPDATE 却被阻塞了?
因为 X 锁可能来自隐式操作。InnoDB 中,UPDATE 和 DELETE 语句在执行时自动为每条命中记录加 X 锁,不需要显式声明。如果你的业务代码里有一句 UPDATE users SET status = 1 WHERE uid = 123 正在运行,而另一条 SELECT * FROM users WHERE uid = 123 LOCK IN SHARE MODE 就会被卡住——不是语法写错了,是 X 锁已经占位了。
更隐蔽的情况:事务 A 执行了 SELECT ... FOR UPDATE,它不仅加了 X 锁,还会在表级自动加上 IX 意向排他锁;此时事务 B 若执行 LOCK TABLES users WRITE(需要表级 X 锁),就会因 IX 与表级 X 冲突而失败,错误日志却只显示 Waiting for table level lock,根本不会提 IX。
- 排查时优先查
SHOW ENGINE INNODB STATUS\G中的TRANSACTIONS和LATEST DETECTED DEADLOCK部分 -
information_schema.INNODB_TRX能看到当前活跃事务及其TRX_STATE(如LOCK WAIT) - 不要只盯 SQL 文本,要结合执行计划确认是否真的走了索引——没索引的 WHERE 容易升级成全表扫描+全表 X 锁
同一个事务里反复加 S 锁为什么不出错?
因为锁是按事务粒度管理的,不是语句级。InnoDB 知道这是“自己人”,第二次 SELECT ... LOCK IN SHARE MODE 会直接复用已持有的 S 锁,跳过兼容性检查。但跨事务就不行:事务 A 持有某行 S 锁未提交,事务 B 尝试对该行加 X 锁,必然等待;事务 B 尝试加 S 锁,则可以立即获得。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
注意这个“复用”仅限于同类型锁。如果事务 A 先加了 S 锁,再执行 UPDATE 想升级为 X 锁,InnoDB 不会自动升级,而是尝试新加一个 X 锁——此时因 S 锁已存在,X 锁申请会失败(除非隔离级别是 SERIALIZABLE 且开启 innodb_locks_unsafe_for_binlog,但这是危险配置,不推荐)。
- 想在事务内安全“读后再改”,直接用
SELECT ... FOR UPDATE,一步到位加 X 锁 - 避免在长事务中先
LOCK IN SHARE MODE再延迟更新,容易被其他事务抢占 X 锁机会 -
FOR UPDATE在可重复读(RR)下还会带间隙锁,而LOCK IN SHARE MODE不会——这点常被忽略,导致幻读或意外阻塞
间隙锁和插入意向锁怎么掺和进 S/X 冲突?
间隙锁(Gap Lock)本身不和 S/X 锁直接冲突,但它和插入意向锁(Insert Intention Lock)互斥;而插入意向锁又和间隙锁互斥。所以真实场景中,你看到的“INSERT 被阻塞”,表面是插入意向锁等间隙锁释放,背后可能是另一个事务持有了覆盖该区间的间隙锁(比如在 RR 隔离级别下执行了 SELECT ... FOR UPDATE 且 WHERE 条件落在空隙上)。
例如表中有 age 值 10、20、30,事务 A 执行 SELECT * FROM t WHERE age = 20 FOR UPDATE(RR 模式),InnoDB 会加记录锁 + 两边间隙锁:(10,20) 和 (20,30)。此时事务 B 执行 INSERT INTO t (age) VALUES (15),就会被阻塞——不是因为 S/X 冲突,而是插入意向锁无法获取,因为它要和 (10,20) 间隙锁冲突。
- 间隙锁只在 RR 隔离级别生效,READ COMMITTED 下会被禁用(但可能带来幻读)
-
INSERT语句本身不加 S/X 锁,但它必须获取插入意向锁,而该锁会和已有间隙锁形成间接阻塞链 - 用
SELECT ... FOR UPDATE时,务必确认 WHERE 是否可能命中空隙——尤其是范围查询或等值查询但对应记录不存在时
实际调试中最容易绕晕的,是把表级意向锁(IS/IX)引发的阻塞,当成行级 S/X 锁冲突来处理。看到 Waiting for table level lock,第一反应不该是“SQL 写错了”,而是去查有没有事务正在执行大范围 FOR UPDATE 或未提交的 DML,悄悄拖住了整张表的锁入口。

















