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

Spring Boot同一实体更新的竞态问题及优化方案问询

问题分析与解决方案

首先直接给结论:是的,Line B-3会覆盖Line A-2的修改,这是典型并发场景下的「丢失更新」问题,我来一步步拆解原因和可行的解决方案:

为什么会出现覆盖?

你给出的两个方法都没有添加@Transactional,这意味着每个数据库操作(findById和save)都是独立的事务:

  1. 请求A执行Line A-1,从数据库读取到旧的User对象(比如年龄20,名字张三)
  2. 请求B执行Line B-1,同样读取到这个旧的User对象
  3. 请求A修改年龄为25(Line A-2,此时只是内存中的对象变化)
  4. 请求B修改名字为李四(Line B-2,同样是内存中的对象变化)
  5. 请求A执行Line A-3,把修改后的User(年龄25,名字张三)写入数据库
  6. 请求B执行Line B-3,把自己内存中的User(年龄20,名字李四)写入数据库——这一步就会覆盖掉请求A对年龄的修改,最终数据库里的用户年龄又变回20了

是不是所有方法都要加@Transactional?

答案是不需要,@Transactional主要用于保证多个数据库操作的原子性(要么全成功要么全回滚),或者利用事务内的实体缓存特性。但仅仅给这两个方法加上@Transactional,并不能解决你遇到的并发覆盖问题——因为两个请求是独立的事务,默认的事务隔离级别(比如MySQL的REPEATABLE READ)下,每个事务还是会读取到自己的快照,最终提交时依然可能覆盖对方的修改。

更优的解决方案

针对这种并发修改场景,推荐以下几种方案,按适用优先级排序:

1. 直接执行数据库更新语句(最推荐)

不要先查询实体再修改保存,而是直接编写更新语句,让数据库原子性地完成修改。比如在Repository中定义方法:

@Modifying
@Query("update User u set u.age = :newAge where u.id = :userId")
void updateAgeById(@Param("userId") long userId, @Param("newAge") int newAge);

@Modifying
@Query("update User u set u.name = :newName where u.id = :userId")
void updateNameById(@Param("userId") long userId, @Param("newName") String newName);

这样每个更新都是数据库层面的原子操作,完全避免了内存中对象和数据库不一致的问题,性能也最高。

2. 使用乐观锁(高并发场景首选)

在User实体中添加版本号字段,利用JPA的乐观锁机制:

@Entity
public class User {
    // 其他字段...
    @Version
    private Integer version; // 版本号,每次更新会自动递增
}

当两个并发请求尝试修改同一个用户时,第二个请求执行save时会发现版本号不匹配(因为第一个请求已经更新了版本号),此时会抛出OptimisticLockingFailureException。你可以捕获这个异常,然后重试操作或者提示用户“数据已更新,请刷新后重试”。这种方式不会阻塞请求,适合高并发场景。

3. 使用悲观锁(低并发场景可选)

在查询用户时直接加写锁,确保同一时间只有一个事务能修改这条记录:

public void updateAge(long userId, int newAge) {
    User theUser = userRepository.findById(userId, LockModeType.PESSIMISTIC_WRITE).orElse(null);
    theUser.setAge(newAge);
    userRepository.save(theUser);
}

这种方式会锁住数据库中的这条记录,其他请求必须等待当前事务提交才能读取和修改,避免了并发覆盖,但会降低并发性能,适合并发量不高的场景。

内容的提问来源于stack exchange,提问作者Chor Wai Chun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 10:48:12