死锁本质是事务设计暴露业务竞争,高频原因为事务粒度大、访问顺序不一致或混入非db操作;须统一多表行锁顺序、避免事务内耗时操作、用select ... for update提前锁定、借助innodb状态日志定位冲突事务。

死锁不是数据库问题,是事务设计暴露了业务竞争
MySQL 的 InnoDB 死锁本身不可完全避免,但高频死锁几乎都源于事务粒度太大、访问顺序不一致、或在事务里混入了非数据库操作(比如调用外部 API、发消息)。它不反映数据库性能差,而说明多个逻辑单元正在以不同路径争抢同一组资源。
必须统一所有写操作的行锁访问顺序
这是最有效也最容易被忽略的预防手段。只要两个事务按相同顺序更新 user 和 order 表,InnoDB 就不会因加锁顺序错位而触发死锁检测。
- 所有涉及多表更新的业务逻辑,强制先查后改,并按主键升序锁定:比如先
SELECT ... FOR UPDATE WHERE id IN (100, 200) ORDER BY id,再更新 - 避免“先更新 A 再查 B,另一个事务先更新 B 再查 A”这类隐式依赖,把关联查询提前到事务开头,用
SELECT ... FOR UPDATE一并锁住 - 如果业务允许,用单条
UPDATE语句完成多字段修改,而不是分多次UPDATE—— 每次语句都会重新计算锁范围
别在事务里做耗时操作,尤其是不能回滚的事
死锁日志里常看到等待时间长达几百毫秒,根源往往是事务中夹杂了 HTTP 调用、文件写入、缓存删除等。这些操作既拖长事务时间,又无法被 InnoDB 回滚,等于把锁持有时间人为放大。
- 把发短信、推消息、删 Redis 这类动作移到事务提交之后,用本地队列或可靠消息中间件兜底
- 避免在事务中调用
sleep()、file_get_contents()或任何可能超时的外部接口 - 如果必须读缓存辅助判断(比如检查库存),用
SELECT ... LOCK IN SHARE MODE而非直接读缓存再决定是否写库,否则缓存和 DB 状态会错位
用 SHOW ENGINE INNODB STATUS 快速定位真凶
死锁日志不是用来“看懂整个流程”的,而是快速识别哪两类事务在打架。重点关注 LATEST DETECTED DEADLOCK 下面的两段 TRANSACTION 块。
- 第一行
mysql tables in use和locked tables能看出是否锁了不该锁的表(比如锁了日志表) - 每段里的
lock_mode X locks rec but not gap表示行锁,lock_mode S locks rec but not gap是共享锁,注意有没有意外的S锁升级成X锁 - 最后的
WE ROLL BACK TRANSACTION (1)表示哪个事务被选为牺牲者,重点看它的 SQL 是否有 ORDER BY 缺失、WHERE 条件没走索引导致锁了全表
真正难处理的不是死锁本身,而是那些靠重试掩盖了逻辑缺陷的场景:比如前端重复提交触发两次事务,或者补偿任务没幂等性,导致每次重试都在制造新冲突。锁是表象,竞争才是本质。











