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

Spring JPA中如何高效管理EntityManager与@Transactional处理大数据集?

批量插入性能优化问题

我尝试向数据库插入约57000个实体,但随着循环推进,插入速度越来越慢。已实现每25条为一批的批量处理,每次执行flush、clear并尝试关闭事务,但并未解决问题。想了解代码中还需进行哪些调整,才能维持插入速率——57K条记录不应耗时4小时以上。

Migrate.java

主类循环遍历Xaction实体,并基于每个Xaction添加XactionParticipant记录:

// Use hibernate cursor to efficiently loop through all xaction entities
String hql = "select xaction from Xaction xaction";
Query<Xaction> query = session.createQuery(hql, Xaction.class);
query.setFetchSize(100);
query.setReadOnly(true);
query.setLockMode("xaction", LockMode.NONE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);

int count = 0;
Instant lap = Instant.now();
List<Xaction> xactionsBatch = new ArrayList<>();
while (results.next()) {
    count++;

    Xaction xaction = (Xaction) results.get(0);
    xactionsBatch.add(xaction);

    // save new XactionParticipants in batches of 25
    if (count % 25 == 0) {
        xactionParticipantService.commitBatch(xactionsBatch);
        float rate = ChronoUnit.MILLIS.between(lap, Instant.now()) / 25f / 1000;
        System.out.printf("Batch rate: %.4fs per xaction\n", rate);
        xactionsBatch = new ArrayList<>();
        lap = Instant.now();
    }
}
xactionParticipantService.commitBatch(xactionsBatch);
results.close();

XactionParticipantService.java

服务提供带有REQUIRES_NEW传播属性的方法,尝试为每个批次关闭事务:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void commitBatch(List<Xaction> xactionBatch) {
    for (Xaction xaction : xactionBatch) {
        try {
            XactionParticipant xp = new XactionParticipant();
            // ... create xp based off Xaction info ...

            // Use native query for efficiency
            String nativeQueryStr = "INSERT INTO XactionParticipant .... xp info/data";
            Query q = em.createNativeQuery(nativeQueryStr);
            q.executeUpdate();
        } catch (Exception e) {
            log.error("Unable to update", e);
        }
    }
    // Clear just in case??
    em.flush();
    em.clear();
}

优化建议

  • 改用批量原生SQL插入:当前循环中每条记录单独执行一次INSERT,数据库交互次数过多。改成单条INSERT语句包含多个VALUES组(如INSERT INTO XactionParticipant (col1, col2) VALUES (?,?), (?,?)...),一次性提交整个批次的参数,能大幅减少数据库往返开销。
  • 增大批次大小:25条的批次太小,建议调整为500-1000条(可根据数据库类型微调,比如MySQL适合较大批次),减少事务开启/关闭、flush的频率。
  • 移除无用的实体创建:代码中创建了XactionParticipant对象但未实际使用,直接从Xaction提取字段拼接参数即可,节省对象初始化的内存和CPU开销。
  • 优化游标与查询配置:
    • 增大setFetchSize的值(如1000),同时确保JDBC驱动支持游标fetch(比如MySQL需在URL中添加useCursorFetch=true),避免一次性加载大量数据到内存。
    • 确认ScrollMode.FORWARD_ONLY生效,禁止反向滚动,减少内存占用。
  • 调整事务与EntityManager清理逻辑:
    • REQUIRES_NEW会为每个批次开启新事务,频繁的事务切换有开销,增大批次后可适当放宽事务粒度。
    • 由于REQUIRES_NEW会自动提交事务,无需手动调用em.flush();em.clear()可以保留,确保EntityManager缓存不会累积无用对象。
  • 关闭二级缓存:如果开启了Hibernate二级缓存,即使设置了readOnly和LockMode.NONE,仍可能缓存Xaction实体导致内存占用上升,临时关闭该实体的二级缓存或全局二级缓存。
  • 数据库层面优化:
    • 临时调整MySQL的innodb_flush_log_at_trx_commit为2(牺牲部分事务一致性换取写入性能,事后可改回)。
    • 确保innodb_buffer_pool_size足够大,减少磁盘IO。
    • 关闭数据库的自动提交(JDBC层面),由应用控制事务提交时机。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 06:55:27