You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仅使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 21:56:04