乐观锁需在update语句的where子句中显式校验版本号或原始值,如“where id = ? and version = ?”,确保仅当数据库当前版本与读取时一致才更新,否则影响行为0,表明并发冲突已发生。

UPDATE 语句里怎么加乐观锁条件
乐观锁不是数据库内置功能,而是靠业务层在 UPDATE 时显式校验版本或数值是否未被改动。核心就是:**更新前确认当前库存值等于你读到的旧值**。
假设库存表叫 product_stock,字段有 id、stock、version(整型版本号),常见写法有两种:
- 用数值比对:
UPDATE product_stock SET stock = stock - 1 WHERE id = 123 AND stock >= 1—— 这能防超卖,但不防并发覆盖(比如两次都读到 stock=5,都减成4) - 用版本号控制:
UPDATE product_stock SET stock = 4, version = version + 1 WHERE id = 123 AND version = 10—— 更严格,只要有人先更新了,version 就变,后续更新就失效
推荐优先用 version 字段,它明确表达了“我基于哪个快照做修改”,比直接比 stock 值更可靠。
为什么 WHERE 后面必须包含 version 或原始值
如果漏掉 WHERE version = ?,那 UPDATE 就退化成无条件覆盖,完全失去乐观锁意义。你可能看到返回影响行数为 0,这就是锁生效的表现——说明数据已被别人改过。
注意几个易错点:
-
SELECT和UPDATE之间不能有长事务或 sleep,否则 version 可能已过期 - 不要用
SELECT ... FOR UPDATE混搭乐观锁,那是悲观锁逻辑,会阻塞,违背乐观初衷 - 如果用
stock做条件(如AND stock = 5),要确保该字段不会被其他非库存逻辑修改,否则条件失效
Java + MyBatis 怎么写才不出错
MyBatis 本身不自动处理乐观锁,得靠你手写 SQL 并检查 update 返回值。
示例 Mapper 方法:
<update id="decreaseStock" parametertype="map">
UPDATE product_stock
SET stock = stock - #{count}, version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
调用后必须判断:
- 返回值为 0 → 更新失败,抛异常或重试(通常重试 2–3 次)
- 返回值为 1 → 成功,继续后续流程(如生成订单)
- 别依赖
SELECT结果做后续判断,因为UPDATE才是原子校验点
并发高时重试策略怎么设
乐观锁天然需要重试,但乱重试会放大数据库压力。实际中要注意:
- 重试间隔建议用指数退避,比如第一次 10ms,第二次 30ms,第三次 100ms
- 最多重试 3 次,再失败就返回“库存竞争激烈,请稍后再试”
- 别在循环里反复
SELECT最新 version —— 直接用原始读到的 version 重试即可,因为你要保证“基于同一快照修改” - 如果业务允许,可把库存扣减和订单创建合并进一个
UPDATE+INSERT SELECT语句,减少往返
真正难的不是写对 SQL,而是让重试不变成雪崩——version 不一致时,系统得快速失败,而不是卡住或无限循环。











