真正安全高效的做法是采用流式写入,即边生成数据边写入输出流,不将整个工作簿加载进内存;EasyExcel支持无模板、无缓存、纯流式导出,配合分页查询与直接响应输出流实现百万级Excel稳定导出。

导出百万级Excel报表时,用传统方式(如Apache POI的SXSSFWorkbook)虽能控制内存,但仍有瓶颈;真正安全高效的做法是采用流式写入(Streaming Write),即边生成数据边写入输出流,不将整个工作簿加载进内存。
用EasyExcel实现真正的流式导出
EasyExcel是基于POI封装的轻量库,支持“无模板、无缓存、纯流式”写入,适合超大数据量场景:
- 不创建Workbook对象在内存中,而是直接向OutputStream写入二进制Excel流(.xlsx格式)
- 通过WriteSheet和WriteTable配合分批写入,每批可设为5000–10000行,避免单次处理过大
- 使用SimpleWriterBuilder或ExcelWriter配合ServletOutputStream,直接响应HTTP下载
示例关键代码:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=report.xlsx");
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
WriteSheet sheet = EasyExcel.writerSheet("数据").build();
// 分页查询数据库,每次查1万条,写入一次
int offset = 0;
int pageSize = 10000;
while (true) {
List<ReportData> data = reportService.listByPage(offset, pageSize);
if (data.isEmpty()) break;
writer.write(data, sheet);
offset += pageSize;
}
}
手动控制流+自定义SAX写入(进阶可控方案)
若EasyExcel无法满足定制需求(如多表头、复杂样式、跨行合并等),可用POI底层的SXSSFSheet + 自定义OutputStream包装器,或更底层的StreamingWriter(需自行拼接OOXML结构):
- 禁用自动flush,手动调用sheet.flushRows(1000)释放内存行缓存
- 用ZipOutputStream直接写入xl/worksheets/sheet1.xml等核心部件(需熟悉Excel OOXML规范)
- 推荐结合Apache POI 5.2+中的StreamingWorkbook(实验性API),它提供writeTo(OutputStream)且不持有完整DOM
数据库与IO协同优化(防卡死)
光靠写入优化不够,源头和管道必须匹配:
- 数据库查询用游标式分页(如MySQL LIMIT offset, size + 合理索引;PostgreSQL用cursor-based pagination避免深分页)
- 禁用JDBC的fetchSize = Integer.MIN_VALUE(启用流式结果集),防止驱动一次性拉取全部结果
- 后端响应设置超时时间(如Spring Boot中server.tomcat.connection-timeout=3600),并开启异步导出+进度回调(避免用户长时间等待)
避坑提醒:哪些“看似流式”实则危险
以下做法仍可能导致OOM,务必避开:
- 用XSSFWorkbook或未配置rowAccessWindowSize的SXSSFWorkbook——整表仍在内存中
- 先收集所有数据到List再传给EasyExcel——百万对象本身已占几百MB堆内存
- 在Controller里new ArrayList()存全量数据,哪怕用了SXSSF——集合容器就是最大内存杀手
- 未关闭流或未用try-with-resources——连接泄漏+临时文件堆积
核心就一条:数据从DB游标读出,经转换后立即写入响应流,中间不落地、不暂存、不建大集合。只要流程串起来,百万行导出也能稳定压测在100MB以内堆内存占用。

















