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 汇总,批次大小按堆内存控制,让大对象用完即释放,内存峰值可控。