仅使用Hibernate乐观锁仍出现死锁,如何解决?
问题分析
你遇到的死锁并非来自乐观锁本身,而是OPTIMISTIC_FORCE_INCREMENT操作触发了数据库的行级排他锁(X锁):
- 普通乐观锁的更新语句带版本号条件(
UPDATE ... WHERE id=? AND version=?),仅当版本匹配时才会加锁更新,不会提前占用锁; - 但
OPTIMISTIC_FORCE_INCREMENT会生成无版本条件的更新语句(UPDATE ... SET version=version+1 WHERE id=?),直接对目标行加锁,这种行为和悲观锁类似,当多个线程并发操作同一实体、或交叉操作多实体且顺序不一致时,就会出现循环等待,触发死锁。
解决方案
要完全依赖Hibernate的版本号乐观锁、避免死锁,可以从以下几点入手:
1. 移除不必要的OPTIMISTIC_FORCE_INCREMENT调用
- 仅在修改关联实体时需要同步更新主实体版本的场景下使用该锁,若只是修改实体自身属性,直接去掉锁调用,让Hibernate自动处理版本号递增。
- 普通乐观锁会在属性修改时自动生成带版本条件的更新语句,不会提前加锁,从根源避免死锁触发。
2. 统一多实体操作的访问顺序
如果业务必须同时更新多个实体,强制所有线程按固定顺序访问实体(比如按实体ID从小到大排序),彻底消除循环等待的可能。
例:所有线程必须先操作ID=1的用户,再操作ID=2的用户,禁止反向操作。
3. 优化Spring Retry的重试逻辑
- 每次重试时必须重新从数据库加载最新实体实例,不能复用之前的实体对象(旧实例的版本号已过期)。
- 示例代码:
@Retryable(value = {ObjectOptimisticLockingFailureException.class}) public void updateUser(Long userId) { // 重试时重新加载实体 ApplicationUser user = userRepository.findById(userId).orElseThrow(); // 执行业务更新 user.setNickname("newNick"); userRepository.save(user); }
4. 避免长事务
尽量缩短事务的执行时间,减少锁的持有时长:
- 把非核心逻辑(比如日志、通知)移出事务范围;
- 避免在事务中进行IO操作(比如调用外部接口、读写文件)。
5. 数据库层面辅助调整(针对Oracle)
- 调整Oracle的死锁检测超时时间(默认约1秒),让死锁更快被检测到,减少阻塞影响;
- 确保没有长事务持有锁不放,定期排查慢SQL和未提交的事务。
内容的提问来源于stack exchange,提问作者Christian
相关产品推荐
相关产品推荐

