NPOI不支持流式导出,因HSSFWorkbook和XSSFWorkbook需全量加载工作簿结构至内存,大数据量下易OOM;OpenXML SDK通过直接写入XML片段实现真正流式导出。
直接用 npoi 做流式导出不可行 —— 它不支持真正的流式写入,openxml sdk 才是唯一能避开内存爆炸、支撑百万行导出的方案。
为什么 Npoi 在 Oracle 大数据量导出时会 OOM
Npoi 的 HSSFWorkbook(.xls)和 XSSFWorkbook(.xlsx)都要求把整个工作簿结构加载进内存。即使你只写一行,它内部仍会初始化默认样式、共享字符串表、sheet 关系等完整对象树。Oracle 查询返回 50 万行时,XSSFWorkbook 往往吃掉 1.5GB+ 内存,IIS 应用池直接回收。
- 典型错误现象:
OutOfMemoryException出现在workbook.CreateSheet()或sheet.CreateRow()后不久 - 不是数据本身大,而是
Npoi对每个单元格都缓存样式引用、字体索引、数字格式 ID -
OracleDataAdapter.Fill()+Npoi组合本质是「全量加载 → 全量建模 → 全量序列化」,三重内存拷贝
OpenXML SDK 如何实现真正流式写入
OpenXML SDK 允许你跳过 Workbook 对象模型,直接用 SpreadsheetDocument.Create() 打开一个可追加的 .xlsx 文件,再用 StreamWriter 方式逐行写入 row XML 片段。关键在于:不调用 SheetData.Append(),而用 SheetData.AddChild() 配合 XmlWriter 边写边刷盘。
- 必须关闭
AutoFilter、ColumnWidth等自动计算功能,否则 SDK 会在 Close() 时遍历全部行反推宽度 - 日期类型要手动转为 Excel 序列值(如
new DateTime(2026,5,15).ToOADate()),不能依赖Cell.DataType = CellValues.Date - 字符串必须走 SharedStringTable,但可预先注册常用值(如“是”“否”“待审核”),避免每行重复插入
- 示例核心片段:
using (var doc = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) { var workbookPart = doc.AddWorkbookPart(); workbookPart.Workbook = new Workbook(); var worksheetPart = workbookPart.AddNewPart<worksheetpart>(); worksheetPart.Worksheet = new Worksheet(new SheetData()); // 此处不 new SheetData(),改用 XmlWriter 直接写入底层流 }</worksheetpart>
Oracle 端必须配合游标分页与无缓冲读取
OracleCommand 默认启用结果集缓冲(FetchSize 未设时可能一次拉 10 万行),这和 OpenXML 的流式目标冲突。必须显式禁用客户端缓存,并用 OracleRefCursor 或分页 SQL 控制吐数节奏。
- 连接字符串加
Fetch Size=1参数(注意:仅对Oracle.ManagedDataAccess有效,旧版System.Data.OracleClient不支持) - 避免
DataTable.Load(),改用OracleCommand.ExecuteReader(CommandBehavior.SequentialAccess) - 逐行读取时,对
CLOB字段必须用GetStream()+StreamReader分块读,否则仍会全载入内存 - 不要用
ROWNUM BETWEEN x AND y分页 —— Oracle 会先排序再截断;改用OFFSET x ROWS FETCH NEXT y ROWS ONLY(12c+)
最容易被忽略的陷阱:字符编码与公式兼容性
OpenXML 输出的 .xlsx 文件默认用 UTF-8,但 Excel 某些版本(尤其是 Windows Server 上的自动化服务)在打开时可能误判为 ANSI,导致中文字段乱码。这不是导出问题,而是 Excel 自身解析缺陷。
- 必须在
WorkbookPart.CoreFilePropertiesPart中显式写入zh-CN - 禁止在单元格中写入任何 Excel 公式(如
=SUM(A1:A10))—— 流式写入无法维护公式依赖树,Excel 打开后会静默转为文本或报错 - 数字类型字段若含千分位或货币符号,必须提前在 C# 层格式化为字符串再写入,
Cell.DataType = CellValues.Number只接受纯数字 - 如果导出后 Excel 提示“发现不可读取的内容”,90% 是因为某行 XML 标签未闭合(比如字符串含未转义的
),需对所有字符串做XmlConvert.EncodeName()











