全局异常处理不负责重试,包括数据库死锁异常;它仅统一捕获、记录和响应,而死锁重试必须前置到业务执行层,通过TransactionTemplate或手动事务控制实现,并需严格限制次数、加入退避延迟及重查状态。

全局异常处理本身不负责重试,包括数据库死锁异常。它只做捕获、记录和响应——死锁发生时事务早已回滚,调用栈已退出,原始参数、上下文、幂等标识等关键信息不可恢复,无法在 @ControllerAdvice 或 GlobalExceptionHandler 中重新执行原操作。
死锁重试必须放在业务执行层
重试逻辑要前置到具体的数据操作位置,确保每次重试都开启全新事务、携带完整上下文,并能校验前置条件。常见方式有:
-
Spring Boot:用
TransactionTemplate包裹核心逻辑,手动控制事务生命周期;在while循环中捕获DeadlockLoserDataAccessException或检查异常消息是否含"Deadlock found when trying to get lock" -
Laravel:直接使用
DB::transaction($callback, $maxRetries),底层自动检测错误码1213(MySQL)或40001(PostgreSQL),并重建事务上下文 -
原生 PDO/SQLAlchemy:显式
beginTransaction()→ 执行 →commit(),失败时判断$e->getCode() === '1213'或orig.args[0] == 1213,再continue重试
重试不是无脑循环,必须加约束
盲目重试会放大问题,甚至引发超卖、重复写入等数据不一致。关键控制点包括:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 最大重试次数设为 2–3 次,超过说明存在设计缺陷(如索引缺失、事务粒度太大)
- 每次重试前插入指数退避延迟,例如
Thread.sleep(50 * retry)或usleep(50000 * pow(2, retry-1)) - 重试前必须重新查询关键状态(如当前库存、订单最新状态),避免“已成功但未返回”的误操作
全局异常处理器能做的辅助事
虽然不驱动重试,但它可以提升整个容错链路的可观测性:
- 对死锁异常打标记录,例如添加日志字段
error.category=deadlock,便于 ELK 聚类分析 - 区分可重试与不可重试异常:若捕获到
BusinessException或ConstraintViolationException,明确标记X-Retryable: false响应头,防止前端误重试 - 集成 TraceID,在异常日志中透出完整调用链,方便定位是哪个服务、哪条 SQL 触发了高频死锁
真正防死锁,靠的是设计而非重试
重试只是兜底手段。降低死锁概率的核心在于数据库访问层设计:
- 所有多表更新严格按约定顺序执行(如总是
user → order → item),消除交叉等待 - 事务粒度最小化:避免在事务内调用 HTTP、读文件、发消息等阻塞操作
- UPDATE/SELECT FOR UPDATE 必须走索引,用
EXPLAIN确认不触发间隙锁或全表扫描 - 高并发写场景优先考虑乐观锁(version 字段)或最终一致性+补偿事务,而非强一致长事务

















