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

SpringBoot中多对多关系双表及关联表的批量插入优化方案

多对多关联表批量插入性能优化问题

我有两张存在多对多关系的表,原本用JPA Repository实现A表、B表及关联表A_B的批量插入,但海量数据场景下JPA性能拉胯。改用JdbcTemplate.batchUpdate实现批量插入,代码如下:

int [][] insertAResults = jdbcTemplate.batchUpdate(
         sqlQueryA,
         List<A>,
         List<A>.size(),
        (PreparedStatement ps, A a) ->{
            ps.setInt(1,a.getId());
            // 其他字段设置
         }
     );
int [][] insertBResults = jdbcTemplate.batchUpdate(
         sqlQueryB,
         List<B>,
         List<B>.size(),
        (PreparedStatement ps, B b) ->{
            ps.setInt(1,b.getId());
            // 其他字段设置
         }
     );
int [][] insertA_BResults = jdbcTemplate.batchUpdate(
         sqlQueryA_B,
         List<Map<IdA,IdB>>,
         List<Map<IdA,IdB>>.size(),
        (PreparedStatement ps, Map<IdA,IdB> ids) ->{
            ps.setInt(1,ids.keySet().stream().findFirst().get());
            ps.setInt(2,ids.values().stream().findFirst().get()); // 修正原代码索引错误
         }
     );

但生成关联表A_B的插入数据时,需要借助另一张表且用到三层嵌套循环,担心这会抵消批量插入的性能优势,请问该怎么优化这三张表的插入操作?


优化方案

1. 用哈希表替代三层嵌套循环,降低时间复杂度

三层嵌套循环的时间复杂度是O(nmk),完全可以用哈希映射将其降到O(n+m):

  • 提前把辅助关联表的数据加载成哈希映射,比如以A的ID为key,对应的B的ID列表为value(根据实际关联逻辑调整映射关系)。
  • 遍历A列表时,直接通过A的ID从哈希表中取出对应的B的ID集合,快速生成A_B的关联数据,彻底避免嵌套循环。

示例代码:

// 提前将辅助表数据加载为哈希映射
Map<Long, List<Long>> aIdToBIds = loadAssociationMappingFromAuxTable();

// 生成A_B批量插入数据
List<Object[]> abBatchData = new ArrayList<>();
for (A a : aList) {
    List<Long> bIds = aIdToBIds.get(a.getId());
    if (bIds != null) {
        for (Long bId : bIds) {
            abBatchData.add(new Object[]{a.getId(), bId});
        }
    }
}

// 批量插入A_B
jdbcTemplate.batchUpdate(
    "INSERT INTO A_B (a_id, b_id) VALUES (?, ?)",
    abBatchData,
    1000, // 分批次处理,避免内存溢出
    (ps, args) -> {
        ps.setLong(1, (Long) args[0]);
        ps.setLong(2, (Long) args[1]);
    }
);

2. 优化JdbcTemplate批量插入的底层配置

  • 开启驱动批量优化:针对MySQL,在JDBC URL中添加rewriteBatchedStatements=true,驱动会自动将多条INSERT合并为单条批量SQL,大幅提升插入效率。
  • 合理设置批次大小:不要直接用列表总大小作为batchSize,建议设置为1000~5000条/批次,平衡内存占用和批量插入效率。

3. 调整事务与插入顺序

  • 如果A、B表用自增ID,先完成A、B的批量插入,拿到所有有效ID后再生成关联数据;如果是业务自定义ID则可并行准备关联数据。
  • 分批次提交事务:不要把所有插入放在一个超大事务里,每处理10万~50万条数据就提交一次,避免事务日志膨胀影响性能。

4. 替换Map为自定义对象,减少存取开销

原代码用Map<IdA,IdB>存储关联ID,每次还要通过stream取值,性能开销大。直接用简单自定义对象存储关联ID,存取更高效:

class ABRelation {
    private Long aId;
    private Long bId;
    // 构造器、getter
}

// 生成关联数据
List<ABRelation> abRelations = new ArrayList<>();
// ... 添加数据逻辑

// 批量插入
jdbcTemplate.batchUpdate(
    sqlQueryA_B,
    abRelations,
    1000,
    (ps, relation) -> {
        ps.setLong(1, relation.getAId());
        ps.setLong(2, relation.getBId());
    }
);

5. 数据库层面优化

  • 插入前暂时关闭表的索引,插入完成后再重建索引——索引会大幅减慢写入速度,海量数据场景下收益明显。
  • 调整数据库写入缓存配置:比如MySQL的innodb_buffer_pool_size、innodb_log_file_size,提升写入吞吐量。
  • 关闭自动提交,统一在事务中提交插入操作,减少磁盘IO次数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:03:17