乐观锁通过版本号校验避免并发更新丢失数据,适合读多写少场景;需增加version字段、SQL带条件更新并检查影响行数;MyBatis-Plus通过@Version注解自动支持;高冲突、跨库、无主键等场景不适用。

SQL并发更新丢数据,本质是多个事务同时读取同一行、各自修改后写回,后写入的覆盖了先写入的变更。乐观锁不靠数据库锁阻塞请求,而是通过版本号或时间戳校验来避免覆盖,适合读多写少、冲突概率低的场景。
乐观锁核心思路:先查后验再改
每次更新前,把当前记录的版本号(或时间戳)一并查出来;执行UPDATE时,WHERE条件中带上这个版本号;如果WHERE匹配不到行,说明期间已被别人更新过,本次更新失败,需重试或提示用户。
- 表结构需增加一个version字段(整型,初始值为0)或updated_at字段(时间戳)
- SELECT语句必须查出version值:SELECT id, name, version FROM user WHERE id = 123
- UPDATE语句必须用version做条件:UPDATE user SET name = '新名字', version = version + 1 WHERE id = 123 AND version = 5
- 检查SQL影响行数:返回1表示更新成功;返回0说明version已变,发生并发冲突
Java代码中怎么集成乐观锁
以MyBatis-Plus为例,只需在实体类字段上加@Version注解,并配置全局乐观锁拦截器,框架会自动拼接version条件和自增逻辑。
- 实体类中声明:private Integer version; 并加上@Version注解
- 配置类里注册MybatisPlusInterceptor,添加OptimisticLockerInnerInterceptor
- 业务层调用updateById(entity)即可,无需手动处理version字段
- 若更新失败(影响行数为0),捕获OptimisticLockException,做重试或友好提示
什么时候不适合用乐观锁
乐观锁不是银弹。当写冲突频繁(比如秒杀库存扣减)、或业务要求强一致性且不能接受重试时,它反而增加复杂度和失败率。
- 高冲突场景下,反复重试可能压垮应用,此时应考虑悲观锁(SELECT ... FOR UPDATE)或队列削峰
- 跨服务、跨库操作无法靠单条SQL保证原子性,乐观锁失效,需分布式锁或Saga等方案
- 没有主键或唯一约束的表,WHERE条件难构造,乐观锁难以落地
- 历史老表无version字段、又不允许加字段时,可临时用updated_at替代,但要注意精度和时钟同步问题
进阶技巧:带条件的乐观更新与批量控制
有些业务需要“仅当状态为A时才允许更新”,这时可以把业务状态和version一起放进WHERE条件,实现复合校验。










