Skip to content

P01 百万数据导出 Excel,解决 OOM ​

面试题:百万级数据导出 Excel,一次性查出来直接 OOM,怎么解决?

先演示问题:为什么单线程导出会 OOM ​

视频里的复现方式:

  • 造 100 万条数据,一次性 SELECT 全查出来,估算占内存 约 1G+;
  • 把应用最大堆设成 1G(-Xmx1g),导出时老年代一路飙升 → OutOfMemoryError。

原因:JDBC 把查询结果封装进 ResultSet 的字节数组,再一批批放进应用层的 List;100 万条全量装进 List 时内存放不下,GC 也回收不掉(List 还被引用着),直接 OOM。

视频方案:多线程分页导出 ​

核心思路:不一次查 100 万,而是分页(每批 25 万)+ 多线程并行导出。

text
① 先查出总数据量(100 万)
② 按每批 25 万计算分页数(100万 ÷ 25万 = 4 批)
③ 循环分批查询,每批交给线程池的一个线程去导出到本地文件
④ 用 CountDownLatch 等待所有线程导出完毕
⑤ 统计总耗时,合并文件
java
long total = queryTotal();            // 100万
int batchSize = 250_000;              // 每批 25万
int pages = (int) Math.ceil(total / (double) batchSize);
CountDownLatch latch = new CountDownLatch(pages);
for (int i = 0; i < pages; i++) {
    executor.submit(() -> {
        try {
            List<Data> list = queryByPage(i, batchSize); // 每批只查25万
            exportToExcel(list);                          // EasyExcel 导出
        } finally {
            latch.countDown();
        }
    });
}
latch.await();

Excel 操作工具用 EasyExcel(底层 POI),单线程版从几十秒优化到多线程约 9 秒。

视频强调的原理:多线程为什么能改善内存 ​

  • 多线程并不会让内存变少(4 批各 250MB 加起来还是 1G);
  • 关键是分批后 JDBC 的 ResultSet 字节数组用完成批就释放,下一批再重新分配:
text
单线程:100万条 → ResultSet 持续往 List 塞 → List 撑爆 → OOM
多线程:每线程只查 25万 → 存完这一批,ResultSet 字节数组释放 → 内存峰值可控
  • 批次大小要根据堆内存决定:如果 25 万条进 List 就能把老年代打满,就要把批次调更小;批次设太大,多线程一样会 OOM。

加分点 ​

  • 思路是通用的:不管用 EasyExcel 还是 POI(SXSSF 流式写),"多线程分页导出"的思路不变;
  • 生产环境再叠加:异步任务 + 下载中心(导出耗时长不阻塞请求)、限制单次导出上限、用 WHERE id > ? 避免深分页;
  • 面试要把"为什么能解决"讲透:不是并发省内存,而是分批查询让大对象不再长期驻留堆内存;
  • 超大批量可考虑导出 CSV、边查边写(JDBC fetchSize/游标)。

一句话总结 ​

单线程全量查询把 ResultSet/List 撑爆导致 OOM;解法是分页 + 多线程分批查询导出 + CountDownLatch 汇总,批次大小按堆内存控制,让大对象用完即释放,内存峰值可控。

基于 VitePress 重建