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

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还有两个关键优化点:

  1. 补全最后一批的提交逻辑:当前循环结束后,剩余不足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();
    }
}
  1. 开启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交互次数。

三、数据库层面的适配优化

  1. 用事务包裹批量操作:在saveList方法上加@Transactional注解,让整个批量插入在一个事务里提交,减少事务开启/提交的开销。
  2. 临时关闭非主键索引:如果插入的表有很多非主键索引,插入时维护索引会耗大量资源。可以先关索引,插完再重建(注意:要确保插入期间没有其他业务依赖这些索引):
ALTER TABLE data_table DISABLE KEYS; -- 关闭非主键索引
-- 执行批量插入
ALTER TABLE data_table ENABLE KEYS; -- 重建索引
  1. 调整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服务器的应用配置

  1. 调整JVM参数:给JVM分配合理的堆内存,避免占用过多系统内存导致swap(swap会让性能暴跌):
-Xmx512m -Xms256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
  1. 拆分插入批次:如果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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 22:20:25