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

遍历列表时批量处理性能表现如何?JPA updateAll大表场景如何优化?

UpdateAll方法性能优化方案

性能劣化根本原因

JPA的save()方法默认执行「先查后写」逻辑:每个实体都会先按主键执行一次SELECT判断是否已存在,存在则更新,不存在则插入。当表数据量较小时,单条SELECT开销可忽略;表数据量达5万级后,单条查询耗时明显升高,3000条数据累计产生3000次查询,整体耗时自然大幅上涨。

可落地优化方案

  • 方案1:批量判断实体状态,消除单条查询开销

    不要依赖JPA单条查询判断实体状态,提前批量查询一次已存在的主键即可:

    // 收集所有非空主键
    List<Long> idList = sas.stream().map(Sa::getId).filter(Objects::nonNull).collect(Collectors.toList());
    // 批量查询已存在的ID,仅1次数据库IO
    Set<Long> existIdSet = saRepository.findAllById(idList).stream().map(Sa::getId).collect(Collectors.toSet());
    // 拆分新增、更新两个列表
    List<Sa> insertList = new ArrayList<>();
    List<Sa> updateList = new ArrayList<>();
    for (Sa sa : sas) {
        if (sa.getId() == null || !existIdSet.contains(sa.getId())) {
            insertList.add(sa);
        } else {
            updateList.add(sa);
        }
    }
    

    拆分后分别处理两个列表即可,这一步可消除99%的查询开销。

  • 方案2:分别优化新增、更新逻辑

    1. 新增逻辑优化:开启Hibernate批量插入配置,在项目配置文件中添加如下参数:
    # MySQL数据库需要在连接串追加参数,开启批量语句合并
    spring.datasource.url=jdbc:mysql://你的数据库地址:端口/库名?rewriteBatchedStatements=true
    # 配置Hibernate批量大小,和你代码中定义的BATCH_SIZE保持一致即可
    spring.jpa.properties.hibernate.jdbc.batch_size=1000
    spring.jpa.properties.hibernate.order_inserts=true
    spring.jpa.properties.hibernate.order_updates=true
    

    配置后saveAll会把多条INSERT语句合并成批量提交,性能可提升3-10倍。
    2. 更新逻辑优化:不要使用save方法,直接写批量更新语句,避免多余的SELECT查询:
    首先在SaRepository中添加自定义批量更新方法,根据你实际需要更新的字段调整即可:

    @Modifying
    @Query("UPDATE Sa s SET s.genericMetrics = :genericMetrics, s.xxx = :xxx WHERE s.id = :id")
    void batchUpdate(@Param("id") Long id, @Param("genericMetrics") String genericMetrics, @Param("xxx") String xxx);
    

    遍历更新列表批量调用该方法后flush即可,也可直接使用JdbcTemplate实现批量更新,性能比JPA更高。

  • 方案3(最优):使用数据库原生UPSERT能力

    主流数据库都支持「存在则更新、不存在则插入」的UPSERT语法,使用该方案不需要拆分新增更新列表,也不需要提前查库,性能完全不受表已有数据量影响。
    以MySQL为例,在SaRepository中添加原生批量UPSERT方法:

    @Modifying
    @Query(value = "INSERT INTO sa (id, generic_metrics, xxx, scena_id) VALUES (:id, :genericMetrics, :xxx, :scenaId) " +
            "ON DUPLICATE KEY UPDATE generic_metrics = VALUES(generic_metrics), xxx = VALUES(xxx)", nativeQuery = true)
    void batchUpsert(@Param("id") Long id, @Param("genericMetrics") String genericMetrics, @Param("xxx") String xxx, @Param("scenaId") Long scenaId);
    

    批量调用该方法处理所有入参即可,3000条数据耗时可稳定保持在3s以内。

  • 基础检查项

    确认Sa表的主键已创建索引,如果你是用主键以外的字段判断实体是否存在,必须给对应字段添加唯一索引,避免查询时全表扫描导致性能随数据量上涨劣化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 15:15:01