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

Spring Boot+Hibernate+PostgreSQL并行更新删除请求死锁问题与事务调优

死锁问题解决办法及事务调优方案

一、死锁原因分析

问题核心在于doSomeWorkAndUpdateSomeEntity()的事务持有锁时间过长:该方法将1.2秒的耗时业务逻辑与数据库更新操作放在同一个事务中,导致实体行的排他锁被长时间占用。当后续的deleteSomeEntity()请求过来时,会因无法获取锁进入等待;而更新事务最终提交时,又会因删除事务的等待状态触发死锁检测,抛出死锁异常。

二、死锁解决办法

1. 缩短锁持有时间(最直接有效)

将耗时的业务逻辑移出事务范围,仅把数据库更新操作放在事务内,大幅缩短锁的持有时间,从根源上减少锁竞争窗口:

// 原方法
@Transactional
public void doSomeWorkAndUpdateSomeEntity(Long id) {
    // 1.2秒耗时业务逻辑
    doLongRunningBusinessLogic();
    // 更新实体
    SomeEntity entity = entityManager.find(SomeEntity.class, id);
    entity.setSomeField("new value");
    entityManager.merge(entity);
}

// 修改后
public void doSomeWorkAndUpdateSomeEntity(Long id) {
    // 非事务执行耗时业务逻辑
    doLongRunningBusinessLogic();
    // 单独启动事务执行数据库更新
    updateSomeEntityInTransaction(id);
}

@Transactional
private void updateSomeEntityInTransaction(Long id) {
    SomeEntity entity = entityManager.find(SomeEntity.class, id);
    if (entity == null) {
        throw new EntityNotFoundException("SomeEntity不存在,ID: " + id);
    }
    entity.setSomeField("new value");
    entityManager.merge(entity);
}

2. 增加冲突检测与重试机制

在删除方法中提前检测实体状态,并设置事务超时,同时捕获死锁异常进行重试:

@Transactional(timeout = 1) // 设置事务超时1秒,避免无限等待
public void deleteSomeEntity(Long id) {
    SomeEntity entity = entityManager.find(SomeEntity.class, id);
    if (entity == null) {
        return; // 实体已被删除,直接返回
    }
    entityManager.remove(entity);
}

在控制器层捕获死锁相关异常(PostgreSQL死锁错误码为40P01),进行2-3次重试:

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
    int retryCount = 0;
    final int MAX_RETRY = 3;
    while (retryCount < MAX_RETRY) {
        try {
            someEntityService.deleteSomeEntity(id);
            return ResponseEntity.noContent().build();
        } catch (PersistenceException e) {
            // 判断是否为PostgreSQL死锁异常
            if (e.getCause() instanceof SQLException 
                && "40P01".equals(((SQLException) e.getCause()).getSQLState())) {
                retryCount++;
                Thread.sleep(100); // 短暂等待后重试
            } else {
                throw e;
            }
        }
    }
    return ResponseEntity.status(HttpStatus.CONFLICT).build();
}

3. 引入乐观锁机制

在实体类中添加版本字段,利用Hibernate的乐观锁避免锁竞争:

@Entity
public class SomeEntity {
    @Id
    private Long id;
    @Version // 乐观锁版本字段
    private Integer version;
    // 其他业务字段及getter/setter
}

此时更新和删除操作会自动校验版本号,若存在并发修改则直接抛出OptimisticLockingFailureException,不会产生锁等待和死锁:

@Transactional
private void updateSomeEntityInTransaction(Long id) {
    SomeEntity entity = entityManager.find(SomeEntity.class, id);
    if (entity == null) {
        throw new EntityNotFoundException("SomeEntity不存在,ID: " + id);
    }
    entity.setSomeField("new value");
    entityManager.merge(entity); // 自动校验版本号
}

@Transactional
public void deleteSomeEntity(Long id) {
    SomeEntity entity = entityManager.find(SomeEntity.class, id);
    if (entity == null) {
        return;
    }
    try {
        entityManager.remove(entity); // 自动校验版本号
    } catch (OptimisticLockingFailureException e) {
        throw new RuntimeException("实体已被其他事务修改,无法删除", e);
    }
}

三、事务调优方案

1. 最小化事务范围

仅将必须的数据库操作包裹在事务中,所有非数据库操作(如业务计算、外部接口调用、MQ发送等)都放在事务外,减少事务资源占用和锁持有时间。

2. 调整事务隔离级别

PostgreSQL默认隔离级别为可重复读,若业务无需该级别保证,可降级为读提交,降低锁竞争概率:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateSomeEntityInTransaction(Long id) {
    // 数据库操作
}

3. 避免长事务

长事务不仅容易引发锁冲突,还会占用数据库连接、增大回滚日志体积。拆分长事务,将复杂业务拆分为多个短事务,必要时通过分布式事务中间件保证一致性(若业务需要)。

4. 数据库层面优化

  • 确保实体主键及常用查询字段有索引,让update/delete操作快速定位行,减少锁等待时间;
  • 开启PostgreSQL死锁日志,方便排查问题:在postgresql.conf中设置
    log_lock_waits = on
    log_deadlocks = on
    

内容的提问来源于stack exchange,提问作者Clarence

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 00:15:00