写点什么

Excel 百万数据高性能导出方案!

  • 2025-05-06
    福建
  • 本文字数:2667 字

    阅读完需:约 9 分钟

前言


在我们的日常工作中,经常会有 Excel 数据导出的需求。


但可能会遇到性能和内存的问题。


今天这篇文章跟大家一起聊聊 Excel 高性能导出的方案,希望对你会有所帮助。


1 传统方案的问题


很多小伙伴门在开发数据导出功能时,习惯性使用 Apache POI 的 HSSF/XSSF 组件。


这类方案在数据量超过 5 万行时,会出现明显的性能断崖式下跌。


根本原因在于内存对象模型的设计缺陷:每个 Cell 对象占用约 1KB 内存,百万级数据直接导致 JVM 堆内存爆炸。


示例代码(反面教材):


// 典型内存杀手写法Workbook workbook = new XSSFWorkbook();Sheet sheet = workbook.createSheet();for (int i = 0; i < 1000000; i++) {    Row row = sheet.createRow(i); // 每行产生Row对象    row.createCell(0).setCellValue("数据"+i); // 每个Cell独立存储}
复制代码


这种写法会产生约 100 万个 Row 对象和 1000 万个 Cell 对象(假设每行 10 列),直接导致内存占用突破 1GB。


更致命的是频繁 Full GC 会导致系统卡顿甚至 OOM 崩溃。


2 流式处理架构设计


高性能导出的核心在于内存与磁盘的平衡


这里给出两种经过生产验证的方案:


方案一:SXSSFWorkbook


使用 SXSSFWorkbook 类,它是 Apache POI 的增强版。


具体示例如下:


// 内存中只保留1000行窗口SXSSFWorkbook workbook = new SXSSFWorkbook(1000); Sheet sheet = workbook.createSheet();for (int i = 0; i < 1000000; i++) {    Row row = sheet.createRow(i);    // 写入后立即刷新到临时文件    if(i % 1000 == 0) {        ((SXSSFSheet)sheet).flushRows(1000);     }}
复制代码


通过设置滑动窗口机制,将已处理数据写入磁盘临时文件,内存中仅保留当前处理批次。实测百万数据内存占用稳定在 200MB 以内。


方案二:EasyExcel


EasyExcel 是阿里巴巴开源的 Excel 高性能处理框架,目前在业界使用比较多。


最近 EasyExcel 的作者又推出了 FastExcel,它是 EasyExcel 的升级版。


// 极简流式API示例String fileName = "data.xlsx";EasyExcel.write(fileName, DataModel.class)    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())    .sheet("Sheet1")    .doWrite(data -> {        // 分页查询数据        int page = 0;        while (true) {            List<DataModel> list = queryByPage(page, 5000);            if (CollectionUtils.isEmpty(list)) break;            data.write(list);            page++;        }    });
复制代码


该方案通过事件驱动模型对象复用池技术,百万数据导出内存占用可控制在 50MB 以下。


其核心优势在于:

  1. 自动分批加载数据(默认每批次 5000 条)

  2. 通过反射缓存消除重复对象创建

  3. 内置样式优化策略避免内存碎片


3 数据库查询的黄金法则


即便导出工具优化到位,若数据查询环节存在瓶颈,整体性能仍会大打折扣。这里给出三个关键优化点:


3.1 解决深度分页问题


传统分页查询在百万级数据时会出现性能雪崩:


SELECT * FROM table LIMIT 900000, 1000 -- 越往后越慢!
复制代码


正确姿势应使用游标方式:


// 基于自增ID的递进查询Long lastId = 0L;int pageSize = 5000;do {    List<Data> list = jdbcTemplate.query(        "SELECT * FROM table WHERE id > ? ORDER BY id LIMIT ?",        new BeanPropertyRowMapper<>(Data.class),        lastId, pageSize);    if(list.isEmpty()) break;    lastId = list.get(list.size()-1).getId();    // 处理数据...} while (true);
复制代码


该方案利用索引的有序性,将时间复杂度从 O(N²)降为 O(N)。


3.2 减少字段数量


-- 错误写法:全字段查询SELECT * FROM big_table 
-- 正确姿势:仅取必要字段SELECT id,name,create_time FROM big_table
复制代码


实测显示,当单行数据从 20 个字段缩减到 5 个字段时,查询耗时降低 40%,网络传输量减少 70%。


3.3 连接池参数调优


# SpringBoot配置示例spring:  datasource:    hikari:      maximum-pool-size: 20 # 根据CPU核数调整      connection-timeout: 30000      idle-timeout: 600000      max-lifetime: 1800000
复制代码


导出场景建议使用独立连接池,避免影响主业务。


连接数计算公式:线程数 = CPU核心数 * 2 + 磁盘数


4 生产级进阶技巧


4.1 异步分片导出


想要提升 Excel 数据导出的性能,我们必须使用多线程异步导出的方案。


具体示例如下:


@Async("exportExecutor")public CompletableFuture<String> asyncExport(ExportParam param) {    // 1. 计算分片数量    int total = dataService.count(param);    int shardSize = total / 100000; 
// 2. 并行处理分片 List<CompletableFuture<Void>> futures = new ArrayList<>(); for (int i = 0; i < shardSize; i++) { int finalI = i; futures.add(CompletableFuture.runAsync(() -> { exportShard(param, finalI * 100000, 100000); }, forkJoinPool.commonPool())); }
// 3. 合并文件 CompletableFuture.allOf(futures.toArray(new CompletableFuture) .thenApply(v -> mergeFiles(shardSize)); return CompletableFuture.completedFuture(taskId);}
复制代码


通过分治策略将任务拆解为多个子任务并行执行,结合线程池管理实现资源可控。


4.2 配置 JVM 参数


我们需要配置 JVM 参数,并且需要对这些参数进行调优:


// JVM启动参数示例-Xmx4g -Xms4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200-XX:ParallelGCThreads=4-XX:ConcGCThreads=2-XX:InitiatingHeapOccupancyPercent=35
复制代码


这样可以有效的提升性能。


导出场景需特别注意:

  • 年轻代与老年代比例建议 2:1

  • 避免创建超过 50KB 的大对象

  • 使用对象池复用 DTO 实例


4.3 整体方案


Excel 高性能导出的方案如下图所示:



用户点击导出按钮,会写入 DB,生成一个唯一的任务 ID,任务状态为待执行。

然后后台异步处理,可以分页将数据写入到 Excel 中(这个过程可以使用多线程实现)。

将 Excel 文件存储到云存储中。

然后更新任务状态为以完成。

最后通过 WebSocket 通知用户导出结果。


5 总结


经过多个千万级项目的锤炼,我们总结出 Excel 高性能导出的黄金公式:


高性能 = 流式处理引擎 + 分页查询优化 + 资源管控


具体实施时可参考以下决策树:



最后给小伙伴们的三个忠告:


  1. 切忌过早优化:在需求明确前不要盲目选择复杂方案

  2. 监控先行:务必埋点记录导出耗时、内存波动等关键指标

  3. 兜底策略:始终提供 CSV 导出选项作为保底方案


希望本文能帮助大家在数据导出的战场上,真正实现"百万数据,弹指之间"!


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18860102

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
Excel百万数据高性能导出方案!_Excel_量贩潮汐·WholesaleTide_InfoQ写作社区