死锁本质是并发事务加锁顺序不一致导致循环等待,解决关键是统一sql执行顺序、缩小事务粒度、显式排序预加锁及捕获1213错误重试。

Java 批量数据库操作中出现 Deadlock,本质不是“批量”本身的问题,而是多个事务在并发执行时,对数据库行/页/表加锁的顺序、范围和持续时间不一致导致的循环等待。关键不在“怎么批量”,而在“怎么让批量操作不打架”。
统一 SQL 执行顺序,打破循环等待
死锁最常见于两个事务以相反顺序更新同一组记录。比如:
- 事务 A:先更新 id=100 的订单,再更新 id=200 的库存
- 事务 B:先更新 id=200 的库存,再更新 id=100 的订单
只要所有批量操作按相同逻辑排序,就能从根源上避免循环依赖。
✅ 实践建议:
- 批量更新前,对主键(或唯一业务键)做 升序排序,再按序执行 SQL 或拼入 IN 子句
- 用
ORDER BY id显式控制多行 UPDATE 的加锁顺序(MySQL InnoDB 中,UPDATE ... WHERE id IN (200,100) 不保证加锁顺序;但加 ORDER BY 后可稳定行为) - 对关联表操作,约定固定先后顺序,例如:总是先锁
orders,再锁order_items,禁止反向调用
缩小事务粒度与锁持有时间
批量操作容易拉长事务时间,放大锁冲突窗口。一个 500 条记录的 UPDATE 若在一个事务里执行,可能持锁数秒;拆成小批次后,每次只持锁几十毫秒,冲突概率大幅下降。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
✅ 实践建议:
- 单批控制在 50~200 条(根据单条耗时和平均锁竞争程度调整)
- 每批提交后短暂休眠(如
Thread.sleep(1)),缓解瞬时压力(非必须,高并发秒杀场景可考虑) - 避免在事务内做远程调用、文件读写、复杂计算等耗时操作
用 SELECT FOR UPDATE + 显式排序预占锁
当批量逻辑需先查后更(如“查出待发货订单→扣减库存→更新状态”),直接 UPDATE 可能因 WHERE 条件未命中索引而锁全表,或引发间隙锁冲突。改用带排序的显式加锁更可控。
✅ 实践建议:
- 用
SELECT id FROM orders WHERE status = 'unshipped' ORDER BY id LIMIT 100 FOR UPDATE先获取有序 ID 列表 - 再用这批 ID 构造精准 UPDATE:
UPDATE orders SET status = 'shipping' WHERE id IN (1,2,...,100) - 确保
WHERE字段有高效索引,避免锁升级为表锁或临键锁扩散
捕获死锁异常并自动重试
即使做了预防,线上高并发下仍可能偶发死锁(尤其跨微服务、多 DB 操作)。这时不能让请求失败,而应优雅重试。
✅ 实践建议:
- 捕获 MySQL 报错码
1213(Deadlock found when trying to get lock) - 使用指数退避重试(如首次 10ms,第二次 30ms,最多 3 次)
- 重试前刷新业务上下文(如重新查最新数据),避免脏读或重复处理
- Spring 项目可用
@Retryable(value = SQLException.class, include = {...}, maxAttempts = 3)简化实现
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










