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

Spring Boot@Transactional传播隔离级别下并发更新丢数据问题

问题分析与解决方案

你的问题核心是并发更新同一行数据的不同字段时出现更新丢失,虽然配置了REQUIRES_NEW和SERIALIZABLE事务,但未解决问题,主要原因可能有以下几点:

可能的原因

  1. 全量更新导致覆盖:你的updateBook方法应该是把整个Book对象转换成BookDBEntity后全量更新所有字段。当多个并发事务同时读取到旧的Book数据,各自修改不同的作者字段,最后提交时,后提交的事务会覆盖先提交事务的修改。
  2. 事务隔离级别与数据库实现差异:SERIALIZABLE隔离级别理论上会让事务串行执行,但部分数据库(如PostgreSQL)的SERIALIZABLE是基于快照隔离实现的,并非严格的串行执行,依然可能出现更新覆盖。
  3. 缓存干扰:如果bookRepository.getBookById读取的是内存缓存(比如Spring Data JPA的一级缓存),即使开启REQUIRES_NEW事务,也可能拿到旧数据,导致更新基于过期状态。

解决方案

方案1:使用乐观锁(推荐)

通过版本号机制实现乐观锁,每次更新时校验版本号,冲突时触发重试,从根本上避免更新丢失。

步骤:

  1. 在BookDBEntity中添加版本号字段:
public class BookDBEntity {
    private String id;
    private String title;
    // 作者字段...
    @Version // JPA乐观锁注解,自动管理版本号
    private int version;
}
  1. 在Service中添加重试逻辑,处理乐观锁冲突:
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class EventSaveBookUseCase implements EventSaveBookPort {
    private final BookRepository bookRepository;
    private final RetryTemplate retryTemplate; // 注入Spring Retry模板

    public EventSaveBookUseCase(BookRepository bookRepository, RetryTemplate retryTemplate) {
        this.bookRepository = bookRepository;
        this.retryTemplate = retryTemplate;
    }

    @Override
    public void updateBook(EventUpdateBookCommand command) {
        retryTemplate.execute(context -> {
            // 每次重试都从数据库读取最新数据
            Book book = bookRepository.getBookById(command.getBookId());
            book.setAuthor(book, command.getAuthor());
            bookRepository.updateBook(book);
            return null;
        });
    }
}
  1. 配置RetryTemplate(Spring Boot示例):
@Configuration
public class RetryConfig {
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3); // 最多重试3次
        retryTemplate.setRetryPolicy(retryPolicy);
        return retryTemplate;
    }
}

方案2:按需更新+行级锁

避免全量更新,只修改需要更新的字段,同时通过行级锁强制串行读取,防止并发读取旧数据。

步骤:

  1. 在Repository中定义按需更新的方法(以JPA为例):
public interface BookRepository extends JpaRepository<BookDBEntity, String> {
    @Query("UPDATE BookDBEntity b SET b.author1Name = :name, b.author1Age = :age WHERE b.id = :id")
    @Modifying
    @Transactional
    void updateAuthor1(@Param("id") String id, @Param("name") String name, @Param("age") int age);

    // 同理实现author2、author3、author4的更新方法
}
  1. 修改Service逻辑,根据命令类型调用对应的更新方法:
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class EventSaveBookUseCase implements EventSaveBookPort {
    private final BookRepository bookRepository;

    public EventSaveBookUseCase(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Override
    public void updateBook(EventUpdateBookCommand command) {
        Author author = command.getAuthor();
        String bookId = command.getBookId();
        // 假设command有authorType字段标识要更新的是哪个作者
        switch(command.getAuthorType()) {
            case AUTHOR1:
                bookRepository.updateAuthor1(bookId, author.getName(), author.getAge());
                break;
            case AUTHOR2:
                bookRepository.updateAuthor2(bookId, author.getName(), author.getAge());
                break;
            case AUTHOR3:
                bookRepository.updateAuthor3(bookId, author.getName(), author.getAge());
                break;
            case AUTHOR4:
                bookRepository.updateAuthor4(bookId, author.getName(), author.getAge());
                break;
        }
    }
}
  1. 如果需要更严格的串行,可以在查询时加行锁:
@Query("SELECT b FROM BookDBEntity b WHERE b.id = :id FOR UPDATE")
BookDBEntity getBookByIdForUpdate(@Param("id") String id);

这样其他事务必须等待当前事务提交后才能读取该行数据,彻底避免并发读取旧数据。

方案3:排查缓存问题

确保bookRepository.getBookById每次都从数据库读取最新数据:

  • 如果使用Spring Data JPA,REQUIRES_NEW事务会创建新的事务上下文,默认不会复用旧的EntityManager缓存;
  • 检查Repository是否自定义了静态缓存逻辑(比如用Map缓存数据),如果有,移除或改为直接从数据库读取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:54:50