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

Spring Boot并发请求致状态表数据错乱的解决方案问询

并发问题解决方案

针对你遇到的同一项目出现多条IS_LATEST=TRUE记录的并发问题,结合你的约束条件(无法修改表结构、不能用存储过程、无法访问前端),以下是几个可行的服务端解决方案:


方案1:修正悲观锁的作用范围,锁定目标行

之前的@Lock可能未作用在正确的查询节点上。需要在查询当前项目最新记录时就加上悲观写锁,确保事务期间其他线程无法修改该行,直到当前事务提交。

代码实现

  1. 在JPA Repository中添加带锁的查询方法:
public interface YourEntityRepository extends JpaRepository<YourEntity, Long> {
    // 查询并锁定当前项目的最新记录
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<YourEntity> findByProjectIdAndIsLatestTrue(Long projectId);

    // 更新该项目所有最新记录为非最新
    @Modifying
    @Query("UPDATE YourEntity e SET e.isLatest = false WHERE e.projectId = :projectId AND e.isLatest = true")
    int updateIsLatestToFalseByProjectId(@Param("projectId") Long projectId);
}
  1. Service层用事务包裹完整流程,并设置合适的隔离级别:
@Service
public class YourService {
    private final YourEntityRepository repository;

    public YourService(YourEntityRepository repository) {
        this.repository = repository;
    }

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void createNewLatestRecord(Long projectId, YourEntity newEntity) {
        // 先锁定当前最新记录,阻塞其他线程的修改操作
        repository.findByProjectIdAndIsLatestTrue(projectId);
        // 将该项目所有旧的最新记录置为非最新
        repository.updateIsLatestToFalseByProjectId(projectId);
        // 插入新的最新记录
        newEntity.setProjectId(projectId);
        newEntity.setIsLatest(true);
        repository.save(newEntity);
    }
}

方案2:数据库显式行锁(针对SQL Server)

如果使用SQL Server,可在更新语句中添加UPDLOCK,HOLDLOCK提示,强制锁定符合条件的行,且持有锁直到事务结束,避免操作交错。

代码实现

修改Repository的更新方法为原生SQL:

@Modifying
@Query(value = "UPDATE your_table SET IS_LATEST = 0 WHERE PROJECT_ID = :projectId AND IS_LATEST = 1 WITH (UPDLOCK, HOLDLOCK)", nativeQuery = true)
int updateIsLatestToFalseByProjectId(@Param("projectId") Long projectId);

Service层保持@Transactional(isolation = Isolation.REPEATABLE_READ)包裹完整流程即可。


方案3:分布式锁(多实例部署场景)

如果你的应用是多实例部署,本地悲观锁和数据库行锁无法跨实例生效,必须用分布式锁确保同一时间只有一个线程操作同一个项目的记录。以下是基于Redis的实现示例:

代码实现

@Service
public class YourService {
    private final YourEntityRepository repository;
    private final StringRedisTemplate redisTemplate;

    public YourService(YourEntityRepository repository, StringRedisTemplate redisTemplate) {
        this.repository = repository;
        this.redisTemplate = redisTemplate;
    }

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void createNewLatestRecord(Long projectId, YourEntity newEntity) {
        String lockKey = "project:lock:" + projectId;
        // 获取分布式锁,设置5秒超时避免死锁
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 5, TimeUnit.SECONDS);
        if (locked == null || !locked) {
            throw new RuntimeException("操作过于频繁,请稍后重试");
        }
        try {
            // 执行更新与插入逻辑
            repository.updateIsLatestToFalseByProjectId(projectId);
            newEntity.setProjectId(projectId);
            newEntity.setIsLatest(true);
            repository.save(newEntity);
        } finally {
            // 无论操作成功与否,释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

方案4:事务内二次检查(乐观锁思路)

在插入新记录前,再次检查该项目是否存在IS_LATEST=TRUE的记录,若存在则回滚事务。该方案适合并发量不高的场景,无需额外锁机制,但可能出现操作失败,需返回错误提示用户重试。

代码实现

@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
public void createNewLatestRecord(Long projectId, YourEntity newEntity) {
    // 更新旧的最新记录
    int updatedRows = repository.updateIsLatestToFalseByProjectId(projectId);
    // 二次检查是否有其他线程已插入新的最新记录
    boolean existsLatest = repository.existsByProjectIdAndIsLatestTrue(projectId);
    if (existsLatest) {
        throw new RuntimeException("已有最新记录,请重试");
    }
    // 插入新的最新记录
    newEntity.setProjectId(projectId);
    newEntity.setIsLatest(true);
    repository.save(newEntity);
}

内容的提问来源于stack exchange,提问作者null-point-exceptional

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:42:40