mybatis-plus-version
MyBatis-Plus 乐观锁
@Version 注解是 MyBatis-Plus 实现乐观锁的核心,它能帮你优雅地解决高并发下的数据更新冲突问题,比如商品超卖、余额扣减等,核心原理可以用一句话概括:基于“版本号”的冲突检测。
🤔 什么是乐观锁?
乐观锁是一种并发控制策略,它假设并发操作发生冲突的概率较低,因此操作数据时不会加锁,而是在数据提交更新时进行冲突检测。若发现数据在此期间已被修改,则当前更新操作会失败。
🧠 核心原理:一个版本号的较量
- 读数据,记版本:当你查询一条记录时,MP会顺手查出它的版本号(
version),比如版本号为1。 - 提交时,验版本:在更新这条数据时,MP生成的SQL会自动加上一个条件:
WHERE ... AND version = 1。 - 无冲突,加一版:如果这期间没人动过这条数据,数据库里的版本号仍然是
1,条件成立,更新成功。同时,MP会把版本号自增为2。 - 有冲突,即失败:如果你查询后,别人抢先一步更新了数据,版本号就变成了
2。这时你再执行更新,SQL中的version = 1条件就无法匹配,更新操作的效果为0行,你就能知道冲突发生了。
🛠️ 使用步骤:三步搞定
第一步:数据库准备
为需要并发控制的表添加一个版本号字段,比如 version。
sql
1 | ALTER TABLE your_table ADD COLUMN version INT DEFAULT 0; |
第二步:实体类添加 @Version 注解
在实体类中,用 @Version 注解标记版本号字段。
java
1 | import com.baomidou.mybatisplus.annotation.Version; |
第三步:注册乐观锁插件
在你的 MyBatis-Plus 配置类中,添加 OptimisticLockerInnerInterceptor 插件。
java
1 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
✨ 效果与实战
完成以上步骤后,当你执行 updateById 等方法时,MP 会自动进行版本控制。
自动生成的SQL:
sql
1 | -- 假设查询到的 version = 1 |
业务代码示例:
java
1 | @Service |
🚦 业务层处理:更新冲突怎么办?
当并发发生时,updateById(product) 会返回 false 或影响行数为 0。你需要根据业务场景,在业务层主动处理冲突,通常有以下几种方式:
- 提示重试:对于用户触发操作,可以返回“数据已被修改,请刷新后重试”。
- 自动重试:对库存扣减等对用户无感的操作,可采用自动重试机制,并建议设置最大重试次数和指数退避策略以避免加剧冲突。
- 合并更新:应用在用户个人资料修改等场景,可尝试合并两次更新,或提示用户确认覆盖。
- 人工介入:在财务等敏感核心系统,可将冲突数据记录下来,交由人工审核处理。
💎 总结与注意
✅ 典型适用场景
- 高并发下的库存扣减:秒杀、抢票系统。
- 账户余额更新:同时发生充值、消费等。
- 多项配置同时修改:避免最后提交覆盖此前内容。
- 多终端/多用户编辑同一数据:编辑协同、后台管理。
⚠️ 几个重要提醒
- 不要手动修改 **
version**:由 MP 自增,手动修改将破坏并发控制逻辑。 - 确保版本号有初始值:新增数据时,确保
version字段有默认值(如 0 或 1)。 - 不支持伪更新:更新时若字段都没变,MP 可能不追加
where version = ?,导致乐观锁失效。可通过UpdateWrapper的setSql强制触发,如setSql("version=version")。 - 注意插件配置:若同时使用分页等多种插件,确保已正确注册
OptimisticLockerInnerInterceptor。 - 支持的数据类型:版本号字段支持
int、Integer、long、Long、Date、Timestamp、LocalDateTime。对于整数类型,版本号自增为oldVersion + 1。
🤔 什么时候不适合用?
- 高冲突场景:数据激烈竞争会导致大量更新失败与重试开销。
- 长事务:事务过长会显著增加冲突概率。
- 需强事务一致性:依赖数据库事务隔离级别或对一致性有极高要求的场景。
MyBatis-Plus 的 @Version 注解实现乐观锁,为高并发下数据一致性问题提供了高效、低侵入的解决方案。它通过版本号机制以无锁方式应对并发,并需要你在业务层配合做好冲突处理。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 小徐的博客!
评论




