遍历列表时批量处理性能表现如何?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:分别优化新增、更新逻辑
- 新增逻辑优化:开启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
相关产品推荐
相关产品推荐

