死锁不是mysql的bug,而是并发设计中事务资源争抢失控的必然结果;需统一表操作顺序、避免间隙锁、缩短事务生命周期,并通过索引优化和sql结构调整根治。

死锁不是MySQL的bug,是并发设计暴露的问题
遇到 Deadlock found when trying to get lock; try restarting transaction 不用慌——InnoDB 已经自动回滚了代价小的那个事务。真正要解决的,是“为什么频繁发生”。高频死锁本质是事务间资源争抢失控,不是调参能根治的,得从代码和SQL结构入手。
统一所有事务的表操作顺序
这是最直接、最有效的预防手段。只要两个事务更新同一组表(比如 orders 和 inventory),就必须强制约定先后顺序。
- ✅ 所有路径都先
UPDATE orders,再UPDATE inventory - ❌ 路径A先改订单后扣库存,路径B先扣库存后改订单——这就是经典死锁温床
- 注意:顺序必须精确到「行级」。比如都按
product_id升序处理多条记录,而不是随意遍历列表 - Spring 里可用
@Transactional包裹统一方法,避免分散在多个 service 层事务中
避免间隙锁(Gap Lock)引发的隐性冲突
在 REPEATABLE READ 隔离级别下,WHERE id > 100 这类范围条件会触发 Next-Key Lock(行锁 + 间隙锁),极易和单行更新形成死锁。
- 检查执行计划:
EXPLAIN SELECT ... WHERE ...确认是否走了索引;没走索引 → 全表扫描 → 锁升级 → 死锁风险飙升 - 范围更新尽量转为等值更新:比如把
UPDATE product SET stock = stock - 1 WHERE category_id = 10拆成按product_id列表逐条更新 - 若必须范围操作,考虑临时降级隔离级别为
READ COMMITTED(会禁用间隙锁,但需评估幻读影响) -
SELECT ... FOR UPDATE必须命中索引,否则锁的是整个索引范围,不是单行
缩短事务生命周期,别让锁“晾着”
锁持有时间越长,与其他事务交叉的概率越高。常见陷阱是事务里混入非DB操作。
- ❌ 在
BEGIN和COMMIT之间做 HTTP 调用、文件写入、复杂计算 - ✅ 把外部依赖提前或延后:先查数据、提交事务,再发消息/调第三方;或先调外部服务,拿到结果后再开启短事务写库
- 大事务拆小:比如批量扣减库存,不要一次
UPDATE ... WHERE id IN (1,2,3,...1000),分批 50 条/次 - 确认
innodb_lock_wait_timeout设置合理(默认 50 秒),避免一个卡住的事务拖垮整条链路
死锁排查容易陷入日志分析,但真正难的是把“统一顺序”“索引覆盖”“事务瘦身”这些原则落地到每一处 SQL 和业务逻辑里。没有银弹,只有持续对齐——每次加新接口,都要反问一句:它和老逻辑锁表顺序一致吗?











