Spring Boot+MySQL批量插入慢及内存溢出问题的优化方案咨询
解决方案
一、先搞定查重环节(速度慢的核心瓶颈)
你当前逐条查重的方式等于1k条数据要发1k次SQL查询,这是最大的性能浪费。直接改成批量查重:
- 把待插入数据的唯一标识(比如业务主键或唯一键)提取出来,用
IN语句一次查完所有已存在的记录 - 示例代码(假设
Data的唯一键字段是uniqueKey):
// 提取所有待插入数据的唯一键 List<String> uniqueKeys = list.stream().map(Data::getUniqueKey).collect(Collectors.toList()); // 一次SQL查完已存在的记录 List<Data> existingDatas = dataRepository.findByUniqueKeyIn(uniqueKeys); // 转成Set方便快速判断 Set<String> existingKeySet = existingDatas.stream().map(Data::getUniqueKey).collect(Collectors.toSet()); // 过滤出真正需要插入的数据 List<Data> toInsertList = list.stream() .filter(data -> !existingKeySet.contains(data.getUniqueKey())) .collect(Collectors.toList());
这一步能把1k次DB请求压缩成1次,直接砍掉绝大多数交互耗时。
二、优化批量插入的实现
你现在的自定义saveList还有两个关键优化点:
- 补全最后一批的提交逻辑:当前循环结束后,剩余不足50条的记录会留在EntityManager缓存里,没及时提交。修改代码:
@Override public void saveList(List<Data> list) { int batchSize = 50; int i = 0; for (Data data : list) { i++; entityManager.persist(data); if (i % batchSize == 0) { entityManager.flush(); entityManager.clear(); } } // 处理最后一批不足batchSize的记录 if (i % batchSize != 0) { entityManager.flush(); entityManager.clear(); } }
- 开启Hibernate真正的批量插入模式:默认情况下Hibernate还是会逐条生成插入SQL,需要在
application.yml加配置强制批量:
spring: jpa: properties: hibernate: jdbc: batch_size: 50 # 和你代码里的batchSize保持一致 order_inserts: true # 强制Hibernate合并同表插入SQL order_updates: true batch_versioned_data: true # 用了乐观锁的话必须开,避免版本号冲突
配置后Hibernate会把50条插入合并成一条INSERT INTO ... VALUES (...), (...), ...的SQL,大幅减少DB交互次数。
三、数据库层面的适配优化
- 用事务包裹批量操作:在
saveList方法上加@Transactional注解,让整个批量插入在一个事务里提交,减少事务开启/提交的开销。 - 临时关闭非主键索引:如果插入的表有很多非主键索引,插入时维护索引会耗大量资源。可以先关索引,插完再重建(注意:要确保插入期间没有其他业务依赖这些索引):
ALTER TABLE data_table DISABLE KEYS; -- 关闭非主键索引 -- 执行批量插入 ALTER TABLE data_table ENABLE KEYS; -- 重建索引
- 调整MySQL内存参数适配1G服务器:避免DB占用过多内存拖垮应用,修改
my.cnf(或my.ini):
[mysqld] innodb_buffer_pool_size = 256M # 分配给InnoDB缓冲池的内存,不要超过服务器内存的1/3 innodb_log_file_size = 64M innodb_flush_log_at_trx_commit = 2 # 牺牲一点实时一致性换插入性能,适合非核心数据 max_allowed_packet = 64M # 确保能容纳批量插入的SQL大小
四、适配1核1G服务器的应用配置
- 调整JVM参数:给JVM分配合理的堆内存,避免占用过多系统内存导致swap(swap会让性能暴跌):
-Xmx512m -Xms256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
- 拆分插入批次:如果1k条一次性处理还是压力大,可以拆成200条一批,分5次处理,降低单次内存占用。
五、终极方案:用原生JDBC批量插入
如果JPA的批量还是不够快,直接跳过ORM用原生JDBC,这是最快的插入方式:
@Autowired private JdbcTemplate jdbcTemplate; public void batchInsert(List<Data> list) { String sql = "INSERT INTO data_table (unique_key, column1, column2) VALUES (?, ?, ?)"; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { Data data = list.get(i); ps.setString(1, data.getUniqueKey()); ps.setString(2, data.getColumn1()); ps.setString(3, data.getColumn2()); } @Override public int getBatchSize() { return list.size(); } }); }
内容的提问来源于stack exchange,提问作者Skelorc
相关产品推荐
相关产品推荐

