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

SpringBoot处理2.32亿条数据遇内存溢出,求解决方案

当前设计的问题分析

你的代码直接将2.32亿条记录全部加载到List<Data>中,这种设计完全不适配如此大规模的数据集。哪怕每条Data对象仅占用100字节(实际远不止),2.32亿条数据也需要至少23GB内存,远超JVM堆内存的常规配置,必然触发OutOfMemoryError。

解决内存溢出的具体建议

1. 改用流式/逐行处理,避免全量加载内存

不要一次性把所有数据读到内存集合里,而是边读边处理。可以通过JdbcTemplate结合手动操作ResultSet实现逐行处理:

修改数据查询/处理代码

@Override
public void processData(Consumer<Data> dataProcessor) {
    jdbcTemplate.setFetchSize(10000);
    jdbcTemplate.execute(conn -> {
        // 关闭自动提交,配合游标使用(部分数据库需要)
        conn.setAutoCommit(false);
        try (PreparedStatement stmt = conn.prepareStatement(getVinDataQuery)) {
            stmt.setFetchSize(10000);
            try (ResultSet rs = stmt.executeQuery()) {
                BeanPropertyRowMapper<Data> rowMapper = new BeanPropertyRowMapper<>(Data.class);
                while (rs.next()) {
                    Data data = rowMapper.mapRow(rs, rs.getRow());
                    // 逐行处理:调用API + 写入目标表
                    dataProcessor.accept(data);
                }
            }
            conn.commit();
        } catch (SQLException e) {
            conn.rollback();
            throw new RuntimeException(e);
        }
        return null;
    });
}

Runner层调用

@Override
public void run(ApplicationArguments args) {
    final long startTimeAt = System.currentTimeMillis();

    DataRepository.processData(data -> {
        // 调用外部API
        ApiResult apiResult = yourApiClient.invoke(data.getTargetField());
        // 写入目标表
        targetTableRepository.save(data, apiResult);
    });

    final long endTimeAt = System.currentTimeMillis();
    log.info("全量数据处理完成耗时: {}", endTimeAt - startTimeAt);
}

2. 确保数据库游标生效

很多数据库(比如MySQL)默认不会启用游标,设置fetchSize也不会生效,依然会一次性加载全量数据。需要在数据源URL中添加对应参数:

  • MySQL:添加useCursorFetch=true,示例:jdbc:mysql://xxx:3306/your_db?useCursorFetch=true&serverTimezone=UTC
  • Oracle:默认支持游标,但需确保fetchSize设置合理

3. 优化内存占用

  • 只查询必要字段:SQL语句中只选择你需要的17位String字段,不要查询无关字段,减少每条Data对象的内存开销
  • 优化实体类:避免使用不必要的字段或大对象,尽量用轻量级类型存储数据

4. 批量操作提升效率

如果API和目标数据库支持批量操作,可以攒一批数据再处理,减少IO次数:

@Override
public void processDataInBatches(int batchSize) {
    jdbcTemplate.setFetchSize(10000);
    List<Data> batch = new ArrayList<>(batchSize);
    jdbcTemplate.query(getVinDataQuery, rs -> {
        BeanPropertyRowMapper<Data> rowMapper = new BeanPropertyRowMapper<>(Data.class);
        Data data = rowMapper.mapRow(rs, rs.getRow());
        batch.add(data);
        if (batch.size() >= batchSize) {
            // 批量调用API
            List<ApiResult> results = yourApiClient.batchInvoke(batch.stream().map(Data::getTargetField).toList());
            // 批量写入目标表
            targetTableRepository.batchSave(batch, results);
            batch.clear();
        }
    });
    // 处理剩余的不足一批的数据
    if (!batch.isEmpty()) {
        List<ApiResult> results = yourApiClient.batchInvoke(batch.stream().map(Data::getTargetField).toList());
        targetTableRepository.batchSave(batch, results);
    }
}

5. 调整JVM堆内存(仅作为辅助)

如果上述优化后仍有内存压力,可以适当调大堆内存,比如启动参数设置-Xmx16G -Xms16G,但这只是临时缓解,不能从根本解决大规模数据全量加载的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 00:42:38