乐观锁冲突处理关键在于业务层及时感知更新失败,MyBatis-Plus 乐观锁不依赖事务但需配合事务保障原子性;UPDATE 必须带 WHERE version = ?,影响行数为0即冲突,须显式判断并重试(限次+退避),绕过 ORM 的操作须手动实现版本校验。

Java 中事务处理乐观锁冲突,关键不在“事务是否开启”,而在于“更新失败后业务层能否及时感知并响应”。MyBatis-Plus 等主流 ORM 的乐观锁机制本身不依赖事务生效,但实际落地时通常配合事务使用,以保障操作原子性与回滚一致性。
事务里怎么写才真正起作用
乐观锁校验发生在 UPDATE 语句执行时,只要 SQL 带 WHERE version = ? 条件,数据库就会做行级比对。事务的作用是:把“读取 → 计算 → 更新”这一整套逻辑包进一个 ACID 单元,避免中间状态被其他事务干扰。例如秒杀扣库存:
- 不加事务:查出库存=10、version=5,计算后发起 UPDATE,但此时另一事务已把 version 改为6——你的 UPDATE 影响行为0,但之前查的数据可能已被缓存或用于下游调用,造成逻辑错乱
- 加事务(@Transactional):整个流程在同一个事务上下文中,失败时可统一回滚,避免脏数据外泄;重试时也能保证重新 select 拿到的是最新快照
影响行数为 0 就是冲突信号,必须处理
MyBatis-Plus 的 updateById() 在检测到 version 不匹配时,不会抛异常,只返回 0。这是设计使然,也是你唯一能拿到的冲突凭证:
- 不能忽略返回值,更不能当成“执行成功”
- 需显式判断:if (result == 0) { /* 冲突分支 */ }
- 事务内冲突后,建议直接 return false 或 throw 自定义异常(如 OptimisticLockException),由 Spring 的 @Transactional 回滚当前事务
重试逻辑要放在事务外,或控制重试边界
事务不适合无限重试:每次重试都应是独立事务,否则会延长锁持有时间,增加死锁风险。推荐做法:
立即学习“Java免费学习笔记(深入)”;
- 主方法不加 @Transactional,由内部 service 方法按需开启(如每次重试都 new 一个事务)
- 或用编程式事务控制,每次重试前手动开启新事务,失败则 rollback
- 限制最大重试次数(如 3 次),超限直接失败,避免雪崩
- 重试间加入微小退避(如 Thread.sleep(10)),降低并发压力
绕过 ORM 的操作天然不触发乐观锁
原生 SQL、JDBC 直连、定时任务批量更新等,若没手写 WHERE version = ? 和 SET version = version + 1,乐观锁就完全失效:
- 这不是事务的问题,而是 SQL 本身没带校验逻辑
- 关键表建议加数据库层防护:比如用 BEFORE UPDATE 触发器校验 version 递增连续性
- 更稳妥的做法是收口所有写操作到统一仓储层,强制校验版本字段


















