乐观锁重试本质是应用层自旋,通过“读-校验-更新-失败-再读”循环实现,不阻塞但持续占用cpu;高并发下易引发重试风暴,需设次数上限、指数退避及业务超时熔断,并从拆分热点、预占机制等源头降冲突。

乐观锁的重试机制本质是应用层自旋
乐观锁本身不加锁,靠提交时比对版本号或时间戳判断冲突。一旦失败,业务代码通常立即重试——这个“读-校验-更新-失败-再读”的循环,就是典型的自旋行为。它不进入阻塞态,线程持续占用 CPU 执行逻辑判断和 SQL 尝试,和底层自旋锁(spin lock)在资源竞争逻辑上高度一致。
高并发下 CPU 飙高的直接诱因是无节制的密集重试
当多个线程争抢同一行热点数据(如库存扣减、账户余额更新),每次 UPDATE 因版本不匹配而失败后立刻重试,就会形成「重试风暴」:
- 每个失败线程都在 while 循环里反复执行查询 + 条件更新 SQL,不释放 CPU 时间片
- 数据库连接池压力同步上升,MySQL 的 parse、execute 阶段持续被高频调用
- 线程数越多,CPU user 时间占比越高,监控上表现为 load 飙升、%sys 不明显、GC 正常
合理设置重试阈值是控制 CPU 消耗的关键防线
不能依赖“重试成功为止”,必须引入可量化的熔断与退让机制:
- 硬性次数上限:如最多重试 3~5 次,超过即抛异常或降级为补偿流程;100 次重试在生产环境等同于拒绝服务
- 指数退避间隔:第 1 次失败后休眠 10ms,第 2 次 30ms,第 3 次 100ms……避免所有线程在同一毫秒级窗口再次撞车
- 结合业务超时:整体操作设定总耗时上限(如 500ms),已用时间 + 预估下次重试开销 > 上限时直接终止
更进一步:从源头降低冲突概率,比优化重试更重要
重试只是兜底,治本在于减少“需要重试”的场景:
- 拆分热点数据:如将单个商品库存拆成 N 个逻辑库存桶,更新时哈希路由,分散写压力
- 预占机制前置:秒杀场景先走 Redis 原子扣减,仅成功者才进入 MySQL 乐观锁更新主库
- 读写分离+本地缓存:非强一致性读走缓存,降低对同一行的读压力,间接减少写事务感知到的版本变化频次










