番摊机器人 关于百万级 Excel 数据导入导出技术的完整梳理。

百万数据 Excel 如何快速导入导出

2026 年 9 月 21 日

核心结论


不能用 POI 的 UserModel 模式(HSSFWorkbook / XSSFWorkbook)。 百万数据直接 OOM。[citation:44.4][citation:44.13]


核心方案:流式处理——读的时候逐行解析不缓存,写的时候逐行写入不保留全量。在这条路上,主流的 Java 方案是 EasyExcel 和 POI SXSSF。[citation:44.2][citation:44.5]


---

一、技术选型对比

导入(读)


| 方案 | 内存占用 | 速度 | 推荐度 |

|:----:|:--------:|:----:|:------:|

| EasyExcel | 极低(SAX 封装,逐行读取) | 快 | ⭐⭐⭐⭐⭐ |

| POI SAX (XSSFReader) | 极低(事件驱动) | 快 | ⭐⭐⭐⭐ |

| POI UserModel | 极高(全量加载) | 慢 | ❌ OOM |

| 原生 CSV 解析 | 极低 | 最快 | ⭐⭐⭐⭐(适合非 xlsx) |

导出(写)


| 方案 | 内存占用 | 速度 | 推荐度 |

|:----:|:--------:|:----:|:------:|

| EasyExcel | 可控(流式写入) | 快 | ⭐⭐⭐⭐⭐ |

| POI SXSSFWorkbook | 可控(磁盘换内存) | 较快 | ⭐⭐⭐⭐ |

| POI XSSFWorkbook | 极高 | 慢 | ❌ OOM |

| CSV 导出 | 极低 | 最快 | ⭐⭐⭐⭐ |


---

二、导入实战:百万行 Excel 入库

2.1 核心原理


EasyExcel 基于 SAX(Simple API for XML)模式——逐行读取 XML 内容,解析一行处理一行,不把整个文件加载到内存。[citation:44.4][citation:44.7]


Excel 文件 → SAX 解析器 → 逐行触发事件 → 回调处理 → 批量写数据库

 内存只保持一行

2.2 代码:EasyExcel 逐行读取


// 自定义监听器,逐行处理

public class DemoDataListener implements ReadListener<DemoData> {


 private static final int BATCH_SIZE = 500;

 private List<DemoData> buffer = new ArrayList<>(BATCH_SIZE);


 @Override

 public void invoke(DemoData data, AnalysisContext context) {

 buffer.add(data);

 if (buffer.size() >= BATCH_SIZE) {

 saveBatch(); // 每500条批写入一次数据库

 }

 }


 @Override

 public void doAfterAllAnalysed(AnalysisContext context) {

 if (!buffer.isEmpty()) {

 saveBatch(); // 处理剩余数据

 }

 }


 private void saveBatch() {

 // JDBC batch insert / MyBatis-Plus saveBatch

 demoMapper.insertBatch(buffer);

 buffer.clear();

 }

}


// 调用——流式读取,不一次性加载全部

EasyExcel.read(file, DemoData.class, new DemoDataListener()).sheet().doRead();

2.3 数据库写入优化


从"逐行插入"到"批量提交",性能差距很大:[citation:44.10]


逐行插入 → 20 分钟(100 万行)

JDBC batch(500 条一批) → 3 分钟

JDBC batch + 事务控制 + 关闭自动提交 → 95 秒

多线程分片写入(多个 sheet 并发) → 40 秒


关键点:[citation:44.7][citation:44.8]

rewriteBatchedStatements=true(MySQL JDBC 必开)

每批 500-2000 条,太大反而慢(事务日志压力)

commit 频率控制在每批一次


---

三、导出实战:从数据库到 Excel

3.1 核心原理


导出最大的坑是先把所有数据查出来装进 List,再一次性写入 Excel——100 万条 × 1KB/条 = 1GB,加上对象开销和 Excel 缓存,内存直接撑爆。[citation:44.13]


正确做法:流式查询 + 流式写入。


数据库游标 → 逐批读取(每批 1000 条) → EasyExcel 逐行写入 → 生成文件

 内存只保留当前批

3.2 代码:流式导出


public void exportLargeExcel(HttpServletResponse response) {

 // 流式写入,不缓存全量

 EasyExcel.write(response.getOutputStream(), ExportData.class)

 .inMemory(false) // 关键:不使用内存模式

 .sheet("数据")

 .doWrite(() -> {

 // 分批查询,每次查 1000 条

 int page = 1;

 List<ExportData> batch;

 while (!(batch = queryBatch(page++, 1000)).isEmpty()) {

 return batch; // 分页返回

 }

 });

}


更可控的做法是手动分批:


try (ExcelWriter excelWriter = EasyExcel.write(fileName, ExportData.class).build()) {

 WriteSheet writeSheet = EasyExcel.writerSheet("数据").build();


 long lastId = 0;

 boolean hasMore = true;

 while (hasMore) {

 // 游标分页(比 OFFSET 更稳定)

 List<ExportData> page = mapper.scrollQuery(lastId, 1000);

 if (page.isEmpty()) {

 hasMore = false;

 } else {

 excelWriter.write(page, writeSheet);

 lastId = page.get(page.size() - 1).getId();

 }

 }

}

3.3 Excel 行数限制怎么办


Excel 单个 Sheet 最多 1,048,576 行。超额有两种处理方式:[citation:44.12][citation:44.16]


| 方案 | 做法 | 适用场景 |

|:----:|------|---------|

| 多 Sheet | 每 100 万行切一个 Sheet | 必须用 .xlsx 格式的场景 |

| CSV 降级 | 超过 100 万行自动转 CSV | 海量导出,用户能接受 CSV |

| 压缩包 | 多个 CSV 打包成 ZIP | 配合定时任务导出 |

3.4 SXSSFWorkbook 备选方案


如果项目已经重度依赖 POI,不想引入 EasyExcel,POI 3.8+ 的 SXSSFWorkbook 是官方提供的流式写出版本:[citation:44.16]


SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 内存中只保留 100 行

// 超过 100 行的数据自动刷入磁盘临时文件

// 内存占用稳定在几 MB


// 写入完成后清理临时文件

workbook.dispose();


SXSSF 与 EasyExcel 的对比:


| 对比项 | EasyExcel | POI SXSSF |

|:-----:|:---------:|:---------:|

| 内存控制 | 流式写入,极低 | 窗口式写入,可控 |

| API 简洁度 | 注解驱动,简单 | 原生 POI 风格,较复杂 |

| 样式支持 | 有但有限 | 完全支持 POI 样式 |

| 社区活跃度 | 阿里开源,长期维护 | Apache 官方,稳定 |


---

四、完整架构


生产环境下,百万数据导入导出一个完整的异步架构通常是这样的:[citation:44.2]


用户请求 → 异步任务表(记录状态)

 ↓

线程池执行 → 流式查询数据库 → 流式写入 Excel

 ↓

完成后 → 上传 OSS/MinIO → 发送通知 → 用户下载


导入方向反过来:

用户上传 → 流式解析 → 校验 → 批量写入数据库

 ↓

 记录失败行 → 生成错误报告


几个关键设计决策:[citation:44.16]

异步执行:百万数据导出不可能在 HTTP 请求内同步完成,必须后台异步

进度反馈:通过 Redis/数据库记录当前处理的行数,前端轮询显示进度

文件管理:生成后上传 OSS/MinIO,临时文件设置过期自动清理

异常处理:导入时的数据校验错误要逐行记录,最终汇总成错误报告


---

五、不同技术栈的对应方案


| 语言 | 推荐库 | 说明 |

|:----:|:------:|------|

| Java | EasyExcel | 阿里开源,SAX 模式,百万级标准方案 |

| Java | POI SXSSF | 官方方案,适合已有 POI 依赖的项目 |

| Python | openpyxl(只读模式)+ pandas | read_only=True 避免 OOM;CSV 用 chunksize |

| .NET | MiniExcel | 针对大数据量优化的轻量库 |

| Node.js | exceljs(流式模式) | 设置 stream 选项,逐行写入 |

| PHP | PhpSpreadsheet + FastExcel | 内存优化模式 |

| 通用 | CSV | 百万行 CSV 解析最快,没有之一 |


---

六、一句话总结

百万数据 Excel 的核心就四个字:流式处理。 读用 SAX 逐行解析(EasyExcel),写用流式逐行写入(EasyExcel / SXSSF),数据库查用游标分页,入库用批量提交。只要做好"不全量加载到内存"这件事,百万行和一千行的处理思路是一样的。