找回密码
 立即注册
首页 业界区 业界 Excel百万数据高性能导出方案!

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

遇玷 2025-6-3 10:22:18
前言

在我们的日常工作中,经常会有Excel数据导出的需求。
但可能会遇到性能和内存的问题。
今天这篇文章跟大家一起聊聊Excel高性能导出的方案,希望对你会有所帮助。
1 传统方案的问题

很多小伙伴门在开发数据导出功能时,习惯性使用Apache POI的HSSF/XSSF组件。
这类方案在数据量超过5万行时,会出现明显的性能断崖式下跌。
根本原因在于内存对象模型的设计缺陷:每个Cell对象占用约1KB内存,百万级数据直接导致JVM堆内存爆炸。
示例代码(反面教材):
  1. // 典型内存杀手写法
  2. Workbook workbook = new XSSFWorkbook();
  3. Sheet sheet = workbook.createSheet();
  4. for (int i = 0; i < 1000000; i++) {
  5.     Row row = sheet.createRow(i); // 每行产生Row对象
  6.     row.createCell(0).setCellValue("数据"+i); // 每个Cell独立存储
  7. }
复制代码
这种写法会产生约100万个Row对象和1000万个Cell对象(假设每行10列),直接导致内存占用突破1GB。
更致命的是频繁Full GC会导致系统卡顿甚至OOM崩溃。
2 流式处理架构设计

高性能导出的核心在于内存与磁盘的平衡
这里给出两种经过生产验证的方案:
方案一:SXSSFWorkbook

使用SXSSFWorkbook类,它是Apache POI的增强版。
具体示例如下:
  1. // 内存中只保留1000行窗口
  2. SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
  3. Sheet sheet = workbook.createSheet();
  4. for (int i = 0; i < 1000000; i++) {
  5.     Row row = sheet.createRow(i);
  6.     // 写入后立即刷新到临时文件
  7.     if(i % 1000 == 0) {
  8.         ((SXSSFSheet)sheet).flushRows(1000);
  9.     }
  10. }
复制代码
通过设置滑动窗口机制,将已处理数据写入磁盘临时文件,内存中仅保留当前处理批次。实测百万数据内存占用稳定在200MB以内。
方案二:EasyExcel

EasyExcel是阿里巴巴开源的Excel高性能处理框架,目前在业界使用比较多。
最近EasyExcel的作者又推出了FastExcel,它是EasyExcel的升级版。
  1. // 极简流式API示例
  2. String fileName = "data.xlsx";
  3. EasyExcel.write(fileName, DataModel.class)
  4.     .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
  5.     .sheet("Sheet1")
  6.     .doWrite(data -> {
  7.         // 分页查询数据
  8.         int page = 0;
  9.         while (true) {
  10.             List<DataModel> list = queryByPage(page, 5000);
  11.             if (CollectionUtils.isEmpty(list)) break;
  12.             data.write(list);
  13.             page++;
  14.         }
  15.     });
复制代码
该方案通过事件驱动模型对象复用池技术,百万数据导出内存占用可控制在50MB以下。
其核心优势在于:

  • 自动分批加载数据(默认每批次5000条)
  • 通过反射缓存消除重复对象创建
  • 内置样式优化策略避免内存碎片
3 数据库查询的黄金法则

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

传统分页查询在百万级数据时会出现性能雪崩:
  1. SELECT * FROM table LIMIT 900000, 1000 -- 越往后越慢!
复制代码
正确姿势应使用游标方式:
  1. // 基于自增ID的递进查询
  2. Long lastId = 0L;
  3. int pageSize = 5000;
  4. do {
  5.     List<Data> list = jdbcTemplate.query(
  6.         "SELECT * FROM table WHERE id > ? ORDER BY id LIMIT ?",
  7.         new BeanPropertyRowMapper<>(Data.class),
  8.         lastId, pageSize);
  9.     if(list.isEmpty()) break;
  10.     lastId = list.get(list.size()-1).getId();
  11.     // 处理数据...
  12. } while (true);
复制代码
该方案利用索引的有序性,将时间复杂度从O(N²)降为O(N)。
3.2 减少字段数量
  1. -- 错误写法:全字段查询
  2. SELECT * FROM big_table
  3. -- 正确姿势:仅取必要字段
  4. SELECT id,name,create_time FROM big_table
复制代码
实测显示,当单行数据从20个字段缩减到5个字段时,查询耗时降低40%,网络传输量减少70%。
3.3 连接池参数调优
  1. # SpringBoot配置示例
  2. spring:
  3.   datasource:
  4.     hikari:
  5.       maximum-pool-size: 20 # 根据CPU核数调整
  6.       connection-timeout: 30000
  7.       idle-timeout: 600000
  8.       max-lifetime: 1800000
复制代码
导出场景建议使用独立连接池,避免影响主业务。
连接数计算公式:线程数 = CPU核心数 * 2 + 磁盘数。
4 生产级进阶技巧

4.1  异步分片导出

想要提升Excel数据导出的性能,我们必须使用多线程异步导出的方案。
具体示例如下:
  1. @Async("exportExecutor")
  2. public CompletableFuture<String> asyncExport(ExportParam param) {
  3.     // 1. 计算分片数量
  4.     int total = dataService.count(param);
  5.     int shardSize = total / 100000;
  6.     // 2. 并行处理分片
  7.     List<CompletableFuture<Void>> futures = new ArrayList<>();
  8.     for (int i = 0; i < shardSize; i++) {
  9.         int finalI = i;
  10.         futures.add(CompletableFuture.runAsync(() -> {
  11.             exportShard(param, finalI * 100000, 100000);
  12.         }, forkJoinPool.commonPool()));
  13.     }
  14.     // 3. 合并文件
  15.     CompletableFuture.allOf(futures.toArray(new CompletableFuture)
  16.         .thenApply(v -> mergeFiles(shardSize));
  17.     return CompletableFuture.completedFuture(taskId);
  18. }
复制代码
通过分治策略将任务拆解为多个子任务并行执行,结合线程池管理实现资源可控。
4.2 配置JVM参数

我们需要配置JVM参数,并且需要对这些参数进行调优:
  1. // JVM启动参数示例
  2. -Xmx4g -Xms4g
  3. -XX:+UseG1GC
  4. -XX:MaxGCPauseMillis=200
  5. -XX:ParallelGCThreads=4
  6. -XX:ConcGCThreads=2
  7. -XX:InitiatingHeapOccupancyPercent=35
复制代码
这样可以有效的提升性能。
导出场景需特别注意:

  • 年轻代与老年代比例建议2:1
  • 避免创建超过50KB的大对象
  • 使用对象池复用DTO实例
4.3 整体方案

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

用户点击导出按钮,会写入DB,生成一个唯一的任务ID,任务状态为待执行。
然后后台异步处理,可以分页将数据写入到Excel中(这个过程可以使用多线程实现)。
将Excel文件存储到云存储中。
然后更新任务状态为以完成。
最后通过WebSocket通知用户导出结果。
5 总结

经过多个千万级项目的锤炼,我们总结出Excel高性能导出的黄金公式:
高性能 = 流式处理引擎 + 分页查询优化 + 资源管控
具体实施时可参考以下决策树:
2.webp

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

  • 切忌过早优化:在需求明确前不要盲目选择复杂方案
  • 监控先行:务必埋点记录导出耗时、内存波动等关键指标
  • 兜底策略:始终提供CSV导出选项作为保底方案
希望本文能帮助大家在数据导出的战场上,真正实现"百万数据,弹指之间"!
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的50万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册