项目上有个导出 excel 场景发现很慢,怎么优化?
- 标签:后端、场景题
🏛 业务架构师
导出 Excel 慢,我会先问三个业务问题定方案档位:数据量多大?谁在用(运营偶尔导,还是财务每月例行)?实时性要求? 导出是典型的内部工具场景,原则是绝不能为了运营的导出把交易库拖挂。
慢的根因(通常三连)
- 一次性
SELECT *全量拉数据,内存和 DB 都扛不住 - POI 的
XSSFWorkbook把整个工作簿放堆内存,几十万行直接 OOM 或分钟级 STW - 同步导出:HTTP 长连接等几十秒,超时断了全白干
分档优化
小数据量(<5 万行):EasyExcel 流式写 + 分页读取(每次查 1000 条),改造半小时,够用。
大数据量(十万到百万行)——异步任务化:
用户点导出 → 创建任务(状态:处理中) → 立即返回
后台: 游标分批读 → EasyExcel 分批写 → 上传OSS → 更新任务状态
用户在"导出中心"页面看到完成 → 下载带时效的链接要点:
- 查询走从库,游标分页(
WHERE id > lastId LIMIT n)代替 OFFSET - EasyExcel 边读边写、分 sheet(单 sheet 上限 104 万行)
- 大文件考虑先导 CSV(体积减半、生成快一倍),Excel 样式对账场景根本不需要
习惯性收尾:导出中心做成通用能力后,顺手解决三个业务问题——审计(谁导了敏感数据,导出是数据泄露高发口)、限流(单人并发 1 个导出任务)、留存(文件 7 天过期,控制存储成本和泄露面)。把一次性性能优化沉淀成平台能力,才是这题在架构层面的答案。
🏗 系统架构师
导出慢的本质通常是两件事:一次性把大数据集拉进内存 + 全量同步等待。我按四步优化。
一、先定位瓶颈 慢在 DB(大查询、深分页 OFFSET)还是慢在写 Excel(POI XSSF 全量 DOM 建模,百万行直接 OOM 或几分钟 GC)?看接口耗时拆解和 GC 日志即可判断。
二、读写双侧全部流式化
- 读:放弃
LIMIT 1000000, 1000这种深分页,改游标分页WHERE id > ? ORDER BY id LIMIT 5000;MyBatis 开流式 cursor(fetchSize 设为 Integer.MIN_VALUE),数据逐批流过而不驻留内存。 - 写:用 EasyExcel 或 POI 的 SXSSFWorkbook(滑动窗口只保留内存中几百行,其余刷盘),边读边写边 flush,内存占用从"数据全量"降到 O(窗口大小)。
三、架构级改造(数据量大到一定程度必走)
- 异步化:点击导出 → 创建任务落库 → 返回"处理中"。后台任务线程池/任务平台分批拉数生成文件 → 上传 OSS → 站内信/邮件发下载链接。用户不再同步等待,接口秒回。
- 隔离:导出走独立的线程池甚至独立实例,避免导出把在线接口的连接池和 CPU 吃光——批处理与在线负载必须物理或逻辑隔离,这是容量设计的基本原则。
- 分片并行:千万级数据按 ID 区间切成 N 片并行生成多个 sheet/文件再合并,多机分摊。
四、数据源降级 超大规模导出本质是离线问题,直接走数仓/ClickHouse 出数,不碰主库。我的分档:10 万行内同步流式导出;以上一律异步任务 + OSS 链路。
💻 全栈开发专家
慢的根因基本都是:一次性查全量 + XSSFWorkbook 全内存建模。我按四步优化。
1. 换流式写:SXSSFWorkbook / EasyExcel
POI 的 XSSFWorkbook 会把整个工作簿的 DOM 放堆里,几十万行直接撑爆;SXSSFWorkbook 滑动窗口只在内存保留 N 行:
java
// 我生产上直接用 EasyExcel,分页查 + 分批写
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream(), OrderDTO.class).build()) {
WriteSheet sheet = EasyExcel.writerSheet("订单").build();
int lastId = 0;
while (true) {
List<OrderDTO> list = orderMapper.pageByCursor(lastId, 2000); // 游标分页
if (list.isEmpty()) break;
writer.write(list, sheet);
lastId = list.get(list.size() - 1).getId();
}
}2. 大数据量改异步导出
超过 10 万行的导出一律异步:用户点导出 → 任务落库 + 线程池生成文件传 OSS → 站内信/邮件给下载链接。避免 HTTP 长连接超时和网关 504。
3. 查询优化
- 用游标分页
WHERE id > ? LIMIT n,深分页LIMIT 1000000, 2000会越来越慢。 - 只 select 导出需要的列;查询和写出并行(生产者-消费者,一边查下一页一边写当前页)。
4. 坑
- Excel 单 sheet 上限 104 万行,超过要分 sheet 或分文件打 zip。
- 响应流导出别忘记设置
Content-Disposition和关闭流;异步导出记得清理过期 OSS 文件。