首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >批量导出几十万行就OOM:SXSSF流式写入与分页查询的落地实践

批量导出几十万行就OOM:SXSSF流式写入与分页查询的落地实践

原创
作者头像
用户9660458
发布于 2026-09-23 10:42:14
发布于 2026-09-23 10:42:14
960
举报

批量导出几十万行就OOM:SXSSF流式写入与分页查询的落地实践

上周三下午快下班,运营跑过来甩了张截图,说批量导出物流查询结果直接把服务搞挂了,日志里一行java.lang.OutOfMemoryError: Java heap space,导出按钮点一次崩一次。数据量也就40万行,测试环境用几百行测都没事。我们花了两天把导出模块从内存全量加载改成流式写入加分页查询,下面把过程记一下。

一、先定位是哪一步把内存撑爆的

原来的代码逻辑很典型:先SELECT *把40万行全查出来塞List,再new XSSFWorkbook()逐行写,最后write到response。XSSFWorkbook整个workbook都在内存里,40万行加上样式对象,堆直接拉满。

代码语言:txt
复制
原始流程:
1. JDBC ResultSet全量加载到List<Map>  -> 40万行对象
2. XSSFWorkbook在内存构建sheet    -> 每一行一个Row对象
3. outputStream.write(workbook.getBytes()) -> 一次性写出

第一步和第二步都在堆里,两个加起来就是OOM的根源。XSSF是DOM模式,整个文档树常驻内存;要解决就得换成SAX式的SXSSF,它只在内存保留一个滑动窗口的行。

二、SXSSF流式写入的关键参数

Apache POI的SXSSFWorkbook可以设置窗口大小,超出窗口的行刷到磁盘临时文件。我们用的参数:

代码语言:java
复制
SXSSFWorkbook wb = new SXSSFWorkbook(500);
Sheet sheet = wb.createSheet("result");
// 每写500行,之前的行flush到磁盘临时文件

窗口大小500是个平衡点:太小会频繁磁盘IO,太大内存降不下来。我们测下来500行窗口,40万行导出时堆占用从原来的1.8GB降到了不到300MB。

这里踩过一个坑:SXSSF默认在系统临时目录生成xml临时文件,跑完不手动dispose会残留磁盘垃圾。必须在finally里调wb.dispose()清临时文件。

三、查询端不能再全量,要分页游标

光换写入还不够,SELECT *全量查40万行照样把JDBC结果集撑爆。改成分页查询,关键是每页按主键游标推进,不能用OFFSET。

代码语言:txt
复制
错误做法:LIMIT offset, size
  - offset=399000时MySQL要扫前399000行再丢弃,越翻越慢

正确做法:WHERE id > lastId ORDER BY id ASC LIMIT size
  - 主键有序,每次从上一次最大id开始,走索引恒定快

每页大小我们设成2000行,查完一页写一页,写完全部再拉下一页。这样任何时刻内存里只有当前页2000行,堆占用稳定。

四、导出前后的对比

环节

改造前

改造后

查询方式

SELECT *全量到List

id游标分页,每页2000

写入方式

XSSFWorkbook全内存

SXSSF窗口500行

堆峰值

1.8GB

约280MB

40万行耗时

OOM直接崩

约45秒完成

临时文件

无

跑完dispose清理

五、5个实际踩过的坑

  1. SXSSF忘了设窗口大小,默认100行但没调dispose,磁盘临时文件堆了几个G。
  2. 分页用了LIMIT offset,翻到最后几页单次要20多秒,换成id游标后恒定0.1秒。
  3. response.setContentType设错成application/octet-stream,前端下载出来文件名乱码。
  4. 导出过程中没设X-Accel-Buffering: no,Nginx缓冲导致前端长时间转圈。
  5. 没设autoClose(OutputStream),连接池里的response流泄漏,导出几次后服务假死。

这次导出需求是配合固乔快递批量查询助手的批量查询结果做的,我主要负责把导出模块从内存全量加载改成流式分页,上线后40万行导出稳定跑通没再崩过。

复盘要点

  1. 大数据量导出必须写入和查询两端一起改,单改一端没用。
  2. SXSSFWorkbook设窗口大小+finally里dispose,查询端用id游标不要用OFFSET。
  3. 注意response的流式响应头和临时文件清理,否则崩得更隐蔽。

以上是个人实践记录,各平台具体功能以官方实时信息为准。

留个问题:你们导出百万行级别数据时,是走POI流式还是直接CSV流?CSV虽然没样式但性能确实差一个量级。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、先定位是哪一步把内存撑爆的
  • 二、SXSSF流式写入的关键参数
  • 三、查询端不能再全量,要分页游标
  • 四、导出前后的对比
  • 五、5个实际踩过的坑
  • 复盘要点
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档