乐观锁需同时满足三条件:where中校验version=?、set中更新version=version+1、检查row_count()是否为0;漏任一环节即失效。

UPDATE 语句漏掉 WHERE version = ? 条件,就不是乐观锁,只是普通更新——这是绝大多数人踩坑的第一步。
UPDATE 必须同时满足三个条件才算真正启用乐观锁
- 显式在
WHERE子句中校验version = ? - 在
SET子句中更新version = version + 1 - 执行后必须检查返回的受影响行数(
ROW_COUNT()),为 0 就代表被并发覆盖
常见错误现象:
- 应用层读出
version = 5,但 SQL 写成UPDATE t SET status = ? WHERE id = ?,没带version条件 - 或者写了
WHERE id = ? AND version = ?,但SET里没更新version字段,导致下次永远卡在version = 5 - 更隐蔽的是:ORM 框架里用了
@Update自定义 SQL,却忘了手动拼version条件,而框架的自动乐观锁机制根本没生效
示例正确写法:
UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = 123 AND version = 5;执行后若返回 0 行影响,说明此时数据库里
version 已不是 5,数据已被他人改过。
SELECT 时必须显式读出 version 字段
所有涉及后续更新的查询,都不能依赖 SELECT * 或漏映射 version。否则应用拿不到原始版本值,UPDATE 里的 WHERE version = ? 只能填默认值(如 0 或 null),一执行就失败。
使用场景包括:
- 编辑页加载数据(不能只查
id, name, stock,必须加version) - 列表页带“操作”按钮的详情预取(比如订单列表点“发货”,要提前读出
version) - MyBatis 的
resultMap必须声明<result column="version" property="version"></result> - JPA 实体类中
@Version字段不能加@Transient,也不能被 JSON 序列化忽略(否则前端回传时丢掉)
如果用 SELECT *,得确认:
- 表结构里
version字段真实存在且未被中间件过滤(比如某些分库分表代理会剔除下划线字段) - MySQL 8.0+ 虽保证列顺序,但字段名仍可能因别名或视图错位——显式写字段最稳。
version 字段类型别用 TIMESTAMP
TIMESTAMP 看似“天然有序”,实际在并发场景下极易倒挂:系统时钟回拨、事务延迟提交、批量更新毫秒级重复,都会让版本号失去单调性。一次倒挂就导致合法更新被误判为冲突。
推荐方案:
- 用
INT UNSIGNED(最大 42 亿,够大多数业务用十年以上) - 高并发或超长生命周期业务用
BIGINT UNSIGNED - 别依赖
GENERATED ALWAYS AS (version + 1):MySQL 8.0+ 支持,但不如手写version = version + 1直观可控,且无法和业务逻辑耦合(比如扣库存失败时不增 version) - 不需要额外索引:
WHERE id = ? AND version = ?一般走主键索引,version单独建索引反而增加写开销
ORM 框架里乐观锁容易失效的几个关键点
MyBatis-Plus 和 JPA 的乐观锁支持,只对特定方法生效,不是“加个注解就全局管用”。
- MyBatis-Plus 的
@Version注解:仅在调用updateById()或lambdaUpdate().eq("id", x).set("status", y)时自动注入version条件;自己写@Update或@Select+ 手动update,就得自己拼 - JPA 的
@Version:只对EntityManager.merge()和save()生效;原生@Modifying @Query不触发校验,等于裸奔 - 最稳妥做法:所有 DAO 更新入口,统一走框架封装的方法,避免切到自定义 SQL;如果必须手写,就在每个
UPDATE里硬编码AND version = #{version}
冲突率持续高于 5%,别急着调优 SQL——先看是不是业务模型本身就不适合乐观锁:多个服务反复争抢同一行的状态字段,本质是共享状态竞争,乐观锁只是把冲突从数据库推到了应用层重试。这时候该拆字段、分表,或者用事件驱动异步收敛。
真正难的不是写对那条 UPDATE,而是确保每次读、每次写、每个框架层、每处重试逻辑,都严格对齐同一个 version 生命周期。漏掉任意一环,乐观锁就形同虚设。











