乐观锁本质是应用层通过版本字段比对实现的并发控制策略,核心为提交时校验version或updated_at;常用UPDATE ... WHERE version = ?实现,要求version有索引且非NULL;时间戳方案存在精度以及时钟同步风险;框架支持仅为自动注入WHERE条件,不处理重试逻辑,且仅保障单行更新一致性。

乐观锁本质是“提交时校验版本”
乐观锁不是数据库内置的锁机制,而是应用层通过字段比对实现的并发控制策略。核心逻辑是:读取数据时记录当前版本(如 version 或 updated_at),更新时带上该版本作为 WHERE 条件;若 WHERE 不匹配(即行已被他人修改),则更新失败,由应用决定重试或报错。
用 UPDATE ... WHERE version = ? 实现最常用
这是最典型的乐观锁写法,依赖一个单调递增的 version 整型字段:
UPDATE users SET name = 'Alice', version = version + 1 WHERE id = 123 AND version = 5;
执行后检查影响行数:
- 影响 1 行 → 更新成功,
version已自增 - 影响 0 行 → 并发冲突,当前数据
version已不是 5
注意:version 字段必须有索引(至少是联合索引的一部分),否则 WHERE 判断可能引发全表扫描;且初始值应为 0 或 1,不能为 NULL。
updated_at 时间戳也能做,但有精度和时钟风险
用 updated_at 替代 version 看似自然,但实际容易出问题:
- 数据库时间精度不足(如 MySQL 5.6 的
DATETIME只到秒级),同一秒内多次更新会“撞版本” - 应用服务器时钟不同步,导致旧时间戳被误判为“未变更”
- 手动设置
updated_at值(如导入数据)会破坏版本语义
如果坚持用时间戳,建议用带毫秒的 TIMESTAMP(3) + 数据库生成(CURRENT_TIMESTAMP(3)),并禁用应用层写入该字段。
MyBatis 和 JPA 的乐观锁支持只是语法糖
MyBatis 的 @Version 注解或 JPA 的 @Version 字段,底层仍是拼 WHERE version = ?。它们不改变 SQL 执行逻辑,只帮你自动管理字段读取和条件注入。
关键点:
- MyBatis-Plus 的
optimisticLockerInnerInterceptor会拦截update方法,自动追加AND version = #{version},但要求实体类字段名必须叫version(或显式配置) - JPA 要求
@Version字段类型只能是int/long/Timestamp,且不可在SET子句中显式赋值,否则会覆盖自动递增 - 这些框架不会帮你处理重试逻辑——冲突后是抛异常、sleep 后重查,还是返回错误码,得自己写
真正容易被忽略的是:乐观锁只保护单条记录更新。跨表、多行、含子查询的更新,无法靠一个 version 字段保证一致性,这时候要么拆成单行操作+重试,要么该上悲观锁或分布式锁。










