lock in share mode 允许两个事务同时加共享锁(s锁),因s锁兼容;但后续update需排他锁(x锁),而s锁与x锁互斥,导致双方等待对方释放s锁,形成死锁。

Lock in share mode 为什么会让两个事务同时成功加锁
因为 LOCK IN SHARE MODE 加的是共享锁(S 锁),而 S 锁之间是兼容的:事务 A 和事务 B 都能对同一行加 S 锁,互不阻塞。这看起来很友好,但问题出在后续操作上——一旦两者都持有了 S 锁,再各自尝试 UPDATE,就会触发锁升级冲突。
UPDATE 时为什么会触发死锁
UPDATE 需要排他锁(X 锁),但当前行已被两个事务以 S 锁占用。InnoDB 不允许 S 锁和 X 锁共存,所以每个事务都必须等对方释放 S 锁才能获得 X 锁——而双方都在等对方先放手,形成循环等待。
- 事务 A 持有 S 锁,请求 X 锁 → 等待事务 B 释放 S 锁
- 事务 B 持有 S 锁,请求 X 锁 → 等待事务 A 释放 S 锁
- InnoDB 检测到该循环后,主动回滚其中一个事务,报错
Deadlock found when trying to get lock
什么时候 LOCK IN SHARE MODE 不会等,反而埋下死锁隐患
关键在于“目标行是否已被其他事务修改过”:
- 如果该行**没被任何事务修改**(即不在活跃的未提交更新中),
SELECT ... LOCK IN SHARE MODE立即返回,两个事务都拿到 S 锁 → 后续UPDATE就容易死锁 - 如果该行**已被另一个事务修改但未提交**,当前
SELECT ... LOCK IN SHARE MODE会直接阻塞等待 → 反而避免了并发加 S 锁,死锁概率降低
也就是说,最危险的情况,恰恰是“一切看起来顺利”的时候:两个事务几乎同时执行 SELECT ... LOCK IN SHARE MODE,都成功了,然后都信心满满地去 UPDATE —— 死锁就发生在第二步。
替代方案比修复更有效
想靠调序、重试或锁超时来缓解,不如直接换锁策略:
- 用
SELECT ... FOR UPDATE替代LOCK IN SHARE MODE:它加的是 X 锁,第二个事务会直接阻塞,不会出现“双 S 锁 + 双升级”路径 - 若业务只需读取后判断再更新,考虑用乐观锁(如
version字段 +WHERE version = ?)避免行锁竞争 - 避免在应用层做“先查后更”的两阶段逻辑;能合并成原子
UPDATE ... WHERE的,优先走单语句
真正容易被忽略的点不是锁怎么加,而是:只要用了 LOCK IN SHARE MODE 做读写分离式更新,就默认接受了“成功读取 ≠ 安全更新”这个事实——而这个间隙,正是死锁温床。











