乐观锁在高冲突场景下会失效而非仅表现差,所有请求空转重试;UPDATE WHERE version=?返回0行是系统警报而非单纯失败,需配合退让策略避免SQL洪峰。

因为乐观锁在写竞争激烈时根本不是“表现差”,而是失效——它连一次有效更新都难以完成,所有请求都在空转重试。
UPDATE WHERE version = ? 返回 0 行不是失败,是系统在发警报
当多个事务几乎同时读取同一行(比如秒杀商品),它们拿到的 version 完全一致。随后并发执行:UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5
只有第一个事务能成功(影响 1 行),其余全部返回 0 行。这不是“慢”,是数据库明确告诉你:“这行数据已被改过,你手里的版本已作废”。
- 应用若无退让逻辑(如指数退避、随机延迟),会立刻发起下一轮
SELECT→UPDATE,形成瞬时 SQL 洪峰 - 每次失败的
UPDATE仍生成undo log并尝试写入,只是最终回滚——这部分开销常被忽略 -
version字段本身增大单行体积,降低 Buffer Pool 缓存效率和页利用率
SELECT FOR UPDATE 不是“更慢”,是把乱序竞争变成有序排队
悲观锁不消除竞争,但改变了竞争形态:BEGIN; SELECT * FROM products WHERE id = 100 FOR UPDATE; 这条语句一执行,后续所有同条件的 FOR UPDATE 就自动排队,谁先拿到锁谁更新。
- 锁持有时间必须短:查完立刻更新,避免在应用层做耗时计算后再
UPDATE - 加锁顺序必须一致:比如扣减多个商品库存,始终按
id升序加锁,否则极易死锁 -
WHERE条件必须走索引:用非主键字段或LIKE '%xxx'会导致锁升级为表锁,直接拖垮并发
冲突率超 15% 时,乐观锁吞吐量反不如悲观锁
这不是理论阈值,而是实测拐点。当真实冲突率超过这个水平,乐观锁的重试循环开始吃掉大量 CPU 和网络资源,而悲观锁的排队等待反而更可控。
- 业务若要求“失败即终止”(如支付状态变更),乐观锁的失败率 = 业务错误率,不可接受
- 版本号机制只适用于单行更新;跨多行、多表的原子操作,乐观锁无法保证整体一致性
- 高冲突场景下,
version字段频繁递增还会加剧 MVCC 的清理压力,影响 purge 线程效率
真正卡住人的从来不是锁类型本身,而是没意识到:乐观锁的有效性极度依赖冲突率。一旦写竞争变真,它就从“轻量方案”变成“隐形雪崩触发器”。


















