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

