乐观锁本质是应用层通过版本字段比对实现的并发控制策略,核心为提交时校验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;
执行后检查影响行数:
version 已自增version 已不是 5注意:version 字段必须有索引(至少是联合索引的一部分),否则 WHERE 判断可能引发全表扫描;且初始值应为 0 或 1,不能为 NULL。
updated_at 时间戳也能做,但有精度和时钟风险用 updated_at 替代 version 看似自然,但实际容易出问题:
DATETIME 只到秒级),同一秒内多次更新会“撞版本”updated_at 值(如导入数据)会破坏版本语义如果坚持用时间戳,建议用带毫秒的 TIMESTAMP(3) + 数据库生成(CURRENT_TIMESTAMP(3)),并禁用应用层写入该字段。
MyBatis 的 @Version 注解或 JPA 的 @Version 字段,底层仍是拼 WHERE version = ?。它们不改变 SQL 执行逻辑,只帮你自动管理字段读取和条件注入。
关键点:
optimisticLockerInnerInterceptor 会拦截 update 方法,自动追加 AND version = #{version},但要求实体类字段名必须叫 version(或显式配置)@Version 字段类型只能是 int/long/Timestamp,且不可在 SET 子句中显式赋值,否则会覆盖自动递增真正容易被忽略的是:乐观锁只保护单条记录更新。跨表、多行、含子查询的更新,无法靠一个 version 字段保证一致性,这时候要么拆成单行操作+重试,要么该上悲观锁或分布式锁。