并发失败后的重试模式
死锁与序列化失败不是异常,而是并发控制机制的正常输出:数据库检测到冲突后主动中止一方,让另一方继续。应用侧必须接住这类错误,回滚事务、短暂退避后重试整个事务。重试成立的前提是事务内的操作可重复执行——一旦事务里夹带了发消息、调外部接口这类不可回滚的副作用,重试就会造成重复执行,破坏业务数据。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| MySQL 可重试错误 | 死锁与锁等待超时两类错误码 |
| PostgreSQL 可重试错误 | 序列化失败、死锁检出、锁不可用三类错误码 |
| 退避策略 | 指数退避叠加随机抖动,避免所有客户端同时重试 |
| 必要条件 | 业务操作可重复执行,或已用唯一约束与幂等键做了保证 |
典型使用场景
- 库存扣减:并发下单时容易撞上锁冲突,重试是标准处理
- 账务记账:两笔资金互转顺序不一致时的死锁处理
- 定时批处理:批量更新遇到锁等待超时,退避后继续推进
- 串行化隔离下的报表写入:冲突中止后自动重跑
- 接口层统一封装:把重试逻辑收敛到一个事务模板里,避免各处重复实现
重试流程骨架
重试流程骨架
1. 开启事务
2. 执行读取与条件更新(带版本号或唯一键校验)
3. 提交事务
失败处理分支:
捕获可重试错误(死锁 / 锁等待超时 / 序列化失败)
回滚事务
随机退避 10 到 200 毫秒后重试
超过最大次数仍失败则向上抛出,由业务决定是否降级
必须遵守的两条:
重试块内不包含不可回滚的外部副作用(发消息、扣外部额度)
重试次数与退避时间都要有上限,避免重试风暴错误码与处理方式
| 数据库 | 错误类别 | 含义 | 处理 |
|---|---|---|---|
| MySQL | 死锁错误码 | 检测到循环等待,回滚代价较小的一方 | 捕获后回滚重试 |
| MySQL | 锁等待超时错误码 | 等待时间超过阈值 | 缩短事务或提高阈值后重试 |
| PostgreSQL | 序列化失败 | 串行化检测到危险结构 | 必须整体重试事务 |
| PostgreSQL | 死锁检出 | 循环等待被打破 | 捕获后回滚重试 |
| PostgreSQL | 锁不可用 | 锁定请求无法立即满足 | 退避后重试或降级 |
重试必须从「事务开头」开始,不能在语句中间续跑:中止发生时事务上下文已经被破坏,继续使用同一连接上的状态会得到难以理解的错误。
统计重试次数很有价值:重试率突然上升通常意味着访问模式变了(批量任务时间调整、索引失效、热点行集中),它比平均耗时更早暴露问题。