java中乐观锁通过版本号实现无锁更新,核心是“先查后验再更新”:读取时获取version,更新时where校验version未变,失败由业务决定重试或提示冲突;version字段需非空默认值,jpa用@version、mybatis需手动写where条件并检查影响行数。

Java 中通过乐观锁思想配合版本号字段实现业务层无锁更新,核心是“先查后验再更新”:读取数据时带上版本号,更新时校验版本未变,若被其他线程修改过则拒绝本次更新,由调用方决定重试或提示冲突。
版本号字段怎么设计
在实体类中添加一个 version 字段,类型通常为 Integer 或 Long,初始值为 0。数据库对应列设为非空、有默认值(如 0),并确保该字段随每次成功更新自动 +1。
- JPA 中可用
@Version注解,框架自动处理版本校验与递增 - MyBatis 需手动在 SQL 中写
WHERE version = #{version},并用#{version + 1}更新新值 - 字段名不强制叫 version,但语义要清晰,避免与业务字段混淆
更新逻辑怎么写才安全
关键不是加锁,而是把“版本比对”作为更新的前置条件,让数据库原子性地完成校验+更新。
- 查询时必须查出当前 version 值(不能只查 ID)
- 构造更新语句时,WHERE 条件包含
id = ? AND version = ? - 执行后检查影响行数:为 1 表示更新成功;为 0 表示版本冲突,需业务侧处理(如抛异常、返回失败、自动重试)
- 不要在 Java 层做 if(version == oldVersion) 再 update —— 这样存在竞态窗口,失去乐观锁意义
怎么应对更新失败
乐观锁天然会失败,重点在于如何优雅处理,而不是避免失败。
- 常见做法是捕获更新失败信号(比如 MyBatis 返回 0 行),重新查最新数据 + 重新计算业务逻辑 + 再次尝试更新
- 可限制重试次数(如最多 3 次),防止无限循环
- 对强一致性要求高的场景(如库存扣减),建议结合补偿机制或降级提示,而非盲目重试
- 日志中记录冲突发生时间、ID、旧版本号,便于问题追溯
和数据库行锁的区别在哪
乐观锁不阻塞其他读写,适合读多写少、冲突概率低的场景;而 SELECT ... FOR UPDATE 是悲观锁,会加行锁,可能引发等待甚至死锁。
- 乐观锁把并发控制逻辑从数据库下推到应用层,减少锁开销
- 版本号更新依赖数据库的 WHERE 条件匹配,本质仍是基于 DB 的原子操作,不是纯内存方案
- 无法解决“ABA 问题”,但业务上 version 是单调递增整数,天然规避了 ABA
- 不适合长事务或需要强一致性的复杂业务流程,此时应考虑分布式锁或事务协调器
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











