在高并发业务中,MySQL 死锁几乎是绕不开的问题。
你可能遇到过这样的报错:
Deadlockfound when tryingtogetlock;tryrestarting transaction死锁并不是 MySQL 的 Bug,而是并发设计不当的必然结果。
本文将从 死锁原理、常见场景、排查方式、设计规范、Java 实战 五个维度,系统讲清楚:MySQL 死锁如何避免?
一、什么是 MySQL 死锁?
- 死锁的定义
死锁(Deadlock) 是指:
多个事务相互持有对方需要的锁,并且都在等待对方释放,导致所有事务永久阻塞。
经典四要素(缺一不可):
条件 说明
- 互斥 锁一次只能被一个事务持有
- 占有并等待 已持有锁的事务继续等待新锁
- 不可剥夺 锁只能由事务主动释放
- 循环等待 多个事务形成等待环
MySQL 的 InnoDB 引擎会主动检测死锁,并回滚代价最小的事务。
二、MySQL 中最常见的死锁场景
场景 1:不同顺序更新相同资源(最常见)
– 事务 A
BEGIN;UPDATE orderSETstatus=1WHEREid=1;UPDATE orderSETstatus=1WHEREid=2;– 事务 B
BEGIN;UPDATE orderSETstatus=2WHEREid=2;UPDATE orderSETstatus=2WHEREid=1;🔴 问题本质:
- A 先锁 id=1,再锁 id=2
- B 先锁 id=2,再锁 id=1
- 顺序不一致 → 循环等待
场景 2:范围更新 + 行更新(间隙锁)
– 事务 A(范围锁)
UPDATE productSETstock=stock-1WHEREcategory_id=10;– 事务 B(单行锁)
UPDATE productSETstock=stock-1WHEREid=100;🔴 在 RR 隔离级别 下:
范围更新会产生 Next-Key Lock(行锁 + 间隙锁)
容易与单行更新形成死锁
场景 3:SELECT … FOR UPDATE 使用不当
SELECT*FROM accountWHEREuser_id=1FORUPDATE;如果:
- 没有命中索引
- 锁住大量行
- 多事务交叉执行
➡️ 极易引发死锁或长时间锁等待
场景 4:唯一索引插入并发冲突
INSERTINTOuser(username)VALUES('tom');多事务并发插入相同唯一键
InnoDB 会先加 共享锁 → 排他锁
顺序不当也可能形成死锁
三、如何快速定位 MySQL 死锁?
1️⃣ 查看最近一次死锁信息(必会)
SHOW ENGINEINNODBSTATUS;重点关注:
LATEST DETECTED DEADLOCK你可以看到:
- 哪些事务
- 执行了哪些 SQL
- 等待什么锁
- 持有什么锁
线上排查死锁的第一利器
2️⃣ 打开死锁日志(推荐)
SETGLOBALinnodb_print_all_deadlocks=1;死锁信息会直接写入 MySQL error log,方便线上分析。
四、避免 MySQL 死锁的 8 条核心原则(重点)
✅ 原则 1:统一访问顺序(最重要)
多表 / 多行更新,顺序必须一致
❌ 错误示例:
- A:订单 → 库存
- B:库存 → 订单
✅ 正确做法:
所有事务:订单 → 库存
✅ 原则 2:尽量使用主键 / 唯一索引更新
UPDATE orderSETstatus=1WHEREid=?;避免:
- 全表扫描
- 范围锁
- 锁定多余行
✅ 原则 3:缩小事务范围(短事务)
❌ 错误:
@Transactionalpublicvoidprocess(){select();业务计算();远程调用();update();}✅ 正确:
select();业务计算();@TransactionalpublicvoidupdateDb(){update();}📌 事务只包数据库操作
✅ 原则 4:避免无索引的 SELECT FOR UPDATE
– 错误(可能锁全表)
SELECT*FROM orderWHEREstatus=0FORUPDATE;– 正确
SELECT*FROM orderWHEREid=?FORUPDATE;✅ 原则 5:减少范围更新,必要时拆分
– 不推荐
UPDATE orderSETstatus=1WHERE create_time<'2024-01-01';– 推荐
分页 / 按主键批量更新
✅ 原则 6:合理设置隔离级别
如果业务允许:
SET SESSION TRANSACTION ISOLATION LEVELREADCOMMITTED;减少间隙锁
显著降低死锁概率
✅ 原则 7:并发场景下控制重试机制
InnoDB 回滚后,应用层应:
try{// db operation}catch(DeadlockExceptione){// sleep + retry}📌 死锁不可怕,不可恢复才可怕
✅ 原则 8:热点资源做串行化设计
例如:
- 库存扣减
- 账户余额
同一订单状态流转
可选方案:
- Redis 分布式锁
- 消息队列串行消费
- 乐观锁(version)
五、Java 高并发场景下的实战建议
1️⃣ 使用乐观锁代替悲观锁
UPDATE productSETstock=stock-1,version=version+1WHEREid=?ANDversion=?;失败则重试,避免大量锁竞争。
2️⃣ 库存 / 金额类操作单线程化
MQ → 单消费者 → DB这是电商、物流系统的常规做法。
3️⃣ 避免“先查再改”的经典坑
❌
SELECT stock FROM productWHEREid=1;UPDATE productSETstock=stock-1WHEREid=1;✅
UPDATE productSETstock=stock-1WHEREid=1AND stock>0;六、总结(架构级结论)
死锁不是偶发事故,而是并发设计问题。
一句话记住:
- 统一顺序
- 索引优先
- 事务要短
- 范围要小
- 必要可重试
- 热点做串行
📌 优秀的系统不是“没有死锁”,而是“死锁可控、可恢复、不影响业务”。