最有效手段是用异常捕获触发动态匹配负载的指数退避重试:精准捕获乐观锁异常(如OptimisticLockingFailureException、VersionConflictException、错误码9007),配合REQUIRES_NEW事务、最新数据重读、幂等校验、异步补偿及降级机制。
直接用异常捕获触发延迟重试,是解决电商高并发下乐观锁冲突最常用也最有效的手段。关键不在于“重试”,而在于“怎么重试”——重试太急会加剧数据库压力,重试太慢影响用户体验,重试无界可能拖垮服务。核心是让重试节奏与系统负载动态匹配。
捕获明确的乐观锁异常类型
不同框架抛出的异常名不同,必须精准识别,避免误捕其他错误:
- JPA/Hibernate:捕获 OptimisticLockingFailureException 或 StaleObjectStateException
- MyBatis(配合 version 字段):SQL 更新返回影响行数为 0 后手动抛出自定义异常,如 VersionConflictException
- TiDB:捕获 MySQL 错误码 9007(Write Conflict),这是分布式事务冲突的明确信号
- 不要 catch Exception 或 RuntimeException —— 这会导致超时、空指针等非冲突类异常也被重试,掩盖真实问题
采用指数退避延迟策略
固定间隔重试(如每次等 100ms)在高并发下极易引发“重试风暴”。指数退避通过拉大重试间隔,天然缓解下游压力:
- 公式为:delay = base × 2ⁿ,其中 base 通常取 50–100ms,n 为当前重试次数(从 0 开始)
- 第 0 次失败后立即重试(delay=50ms),第 1 次等 100ms,第 2 次等 200ms,第 3 次等 400ms……
- 务必设置上限:总重试次数 ≤ 5,或累计等待时间 ≤ 800ms(避免用户端长时间无响应)
- 可叠加随机抖动(jitter),例如 delay × (0.5–1.5),进一步打散重试时间点,防雪崩
重试必须绑定新事务与最新数据
重试不是简单地再跑一遍原方法——它必须重新开启事务,并重新读取最新数据状态:
- 在 Spring 中,被重试的方法需标注 @Transactional(propagation = Propagation.REQUIRES_NEW),确保每次重试都在干净事务中执行
- 重试前必须重新查询商品/库存记录,获取最新 version 和当前可用值(例如剩余库存),不能复用旧对象
- 若业务逻辑含多步(如扣库存 + 写订单 + 发消息),重试范围应覆盖整个原子操作,而非仅更新语句
- 对用户侧,可返回 “正在处理,请稍候”,而非直接报错;前端可静默轮询结果,避免用户反复点击提交
结合幂等与降级保障最终可用性
重试本身不是银弹,需配套机制兜底:
- 所有重试入口方法必须具备幂等性:依赖唯一业务单号(如 order_no)做数据库唯一索引或 Redis setnx 校验,防止成功后又因网络重传导致重复扣减
- 达到最大重试次数仍未成功,不应直接抛错给前端,而是转入异步补偿队列(如 RocketMQ 延迟消息),由后台任务择机重试或人工介入
- 对非核心路径(如优惠券发放、积分更新),可配置开关,在流量高峰时自动跳过乐观锁校验,降级为“尽力而为”模式
- 记录每次重试日志,包含 retry_count、elapsed_ms、version_before、version_after,便于定位冲突热点商品

















