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
相关产品推荐
相关产品推荐

