乐观锁在高冲突场景下性能急剧恶化:因频繁重试导致cpu/网络空转、数据库压力升高,而悲观锁通过有序排队将无序竞争转化为可调度性。

乐观锁在写冲突频繁时性能不会“下降”,而是直接失效——它根本没机会完成更新,应用层反复重试、失败、再重试,CPU和网络耗在空转,数据库压力反而升高。
乐观锁重试机制在高冲突下变成恶性循环
当多个事务几乎同时读取同一行(比如秒杀商品库存),它们拿到的 version 值完全一样。随后并发执行 UPDATE ... WHERE id = ? AND version = ?,结果只有第一个事务能成功(影响 1 行),其余全部返回 0 行影响。这时业务代码若按默认逻辑重试,就会立刻再查、再比、再撞——没有退让、没有排队、只有竞争。
- 重试不是“等一会儿再试”,而是立即发起新查询,可能瞬间压出数倍于实际请求量的 SQL
- 每次重试都触发一次全表扫描(若没走索引)或至少一次主键回表,IO 和 CPU 成倍增加
- 应用线程池被阻塞在重试循环里,无法释放,容易引发下游超时雪崩
SELECT FOR UPDATE 不是“更慢”,而是把不确定性转为可调度性
SELECT FOR UPDATE 在读阶段就加 X 锁,后续更新自然能成功。它不消除竞争,但把竞争从“无序碰撞”变成“有序排队”。关键前提是:事务必须短、锁范围要小、且加锁顺序一致。
- 锁只在事务内有效,
BEGIN后执行SELECT ... FOR UPDATE,然后尽快UPDATE+COMMIT,避免锁持有时间过长 - 必须确保所有业务路径对同一组记录使用相同顺序加锁(例如:总是先锁商品 ID 小的,再锁大的),否则会触发死锁
- 若 WHERE 条件未命中索引(如用非主键字段或模糊匹配),
FOR UPDATE可能升级为表锁,彻底拖垮并发能力
版本号字段本身在高冲突下成为新瓶颈
每次成功更新都要递增 version 字段,这看似简单,但在高频写场景下,它会带来两个隐藏开销:
-
version字段使每行数据变大,间接影响 Buffer Pool 缓存效率和磁盘页利用率 - 当
UPDATE因 version 不匹配失败时,MySQL 仍会生成 undo log 并尝试写入,只是最终回滚——这部分开销常被忽略 - 如果业务要求“失败即告终”而非重试(如支付状态变更),那乐观锁的失败率就是直接的业务错误率,不可接受
真正卡住人的从来不是锁类型本身,而是没意识到:乐观锁的有效性极度依赖冲突率。一旦真实冲突超过 15%,它的吞吐量往往不如一个锁粒度精准、事务边界清晰的悲观锁方案。











