千万级Excel导出需用CompletableFuture实现动态分片写入与合并:按主键/时间分片、每片携带动态schema、线程独立生成临时文件、主线程合并并自动分sheet,兼顾性能与稳定性。
直接用有返回值的多线程导出千万级 excel,关键不在“多线程”本身,而在于如何让每个线程真正独立完成一段可验证、可合并、不冲突的写入任务——尤其在动态报表引擎中,表头不定、字段类型不一、数据结构嵌套,还要求最终生成一个逻辑统一的 excel 文件(不是多个文件或 zip 包)。
动态列 + 分片查询:先拆得清楚,才能并得安全
动态报表意味着导出前才确定字段名、顺序、格式甚至是否合并单元格。不能靠预定义 class 绑定,必须运行时解析元数据。建议做法:
- 从报表配置中心或 SQL 查询结果元信息中实时提取列定义(字段名、类型、宽度、是否冻结、是否合计等)
- 将总数据量按主键/时间范围分片(如每片 5 万条),确保各片数据互斥、无重叠
- 每个分片携带完整列描述信息(即“动态 schema”),传给对应线程,避免共享全局模板引发并发修改异常
带返回值的线程任务:用 CompletableFuture 封装单片写入结果
别用 Runnable 或普通 Future,要用 CompletableFuture<File> 或 CompletableFuture<byte[]>,让每个线程明确返回自己写好的临时文件路径或字节数组。这样后续才能可靠合并:
- 每个线程调用 EasyExcel.write() 写入一个独立 .xlsx 临时文件(注意:不是同一个 Workbook 实例)
- 写完立即关闭流,返回该文件的绝对路径(或内存中 byte[])
- 主线程 collect 所有 CompletableFuture,用 allOf().join() 等待全部完成
- 最后用 Apache POI 的 XSSFWorkbook 或 EasyExcel 的 mergeSheet 工具类,把多个临时文件的 sheet 合并进一个目标文件(注意:EasyExcel 原生不支持跨文件合并,需自行封装或用 POI 补位)
线程池与资源控制:10 核心线程是多数场景的安全起点
千万级导出不是拼线程数,而是平衡数据库连接、磁盘 IO 和 JVM 内存。参考配置:
- 线程池核心数设为 8–12,与数据库最大连接数(如 HikariCP 的 maximumPoolSize)保持一致或略小
- 拒绝策略用 CallerRunsPolicy,防止队列积压导致 OOM
- 每个线程内严格使用分页查询(limit + offset 或游标式),禁用全表 select *;写入时启用 EasyExcel 的 writeSheet().autoCloseStream(true)
- 临时文件写入 SSD 目录,并设置 JVM -XX:+UseG1GC,避免大对象触发 Full GC
合并与兜底:单个 sheet 超 104 万行?自动切 Sheet 不手软
Excel 单 sheet 行数上限是硬约束(1,048,576)。千万数据不可能塞进一张 sheet,必须按规则拆:
- 合并阶段检测每个临时文件的 sheet 行数,超阈值(如 95 万)就拆成两个 sheet,用序号后缀命名("数据_1", "数据_2")
- 最终输出文件中,所有 sheet 按原始分片顺序排列,首行统一写动态生成的表头(由主线程统一渲染一次)
- 提供失败重试机制:对某一分片写入失败的 CompletableFuture,记录 error log 并跳过,不影响整体流程,最后在 Excel 最后一个 sheet 插入错误摘要


















