You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

导出XLSX数据时遇OutOfMemoryException问题求助(超6万条数据)

解决OpenXML导出大量数据时的OutOfMemoryException问题

我完全懂你现在的困扰——用OpenXML SDK的常规方式处理6万+条数据时,内存溢出真的是大概率事件。你当前的代码是把所有行、单元格对象都攥在内存里,直到整个工作表构建完才写入磁盘,这种方式对付小数据量没问题,但数据一上来内存肯定扛不住。咱们来一步步解决这个问题:

问题根源

你现在用的是OpenXML的**DOM(文档对象模型)**模式,这种模式会把Excel文档的所有元素(每一行、每一个单元格)都作为对象存在内存里。当数据量冲到6万条时,这些对象的内存占用会急剧膨胀,最终触发OutOfMemoryException。而CSV导出正常,是因为CSV是纯文本流式输出,不需要维护复杂的对象结构。

解决方案:用SAX模式流式写入

OpenXML SDK支持**SAX(Simple API for XML)**模式,也就是流式写入逻辑。这种模式下,我们会逐行生成XML内容并直接写入磁盘,内存里只会保留当前处理的那一行数据,不会把整个文档都存着。再搭配共享字符串表,还能进一步降低内存占用和最终的文件大小。

修改后的示例代码

下面是适配你场景的优化代码,用OpenXmlWriter实现流式写入:

// 先创建共享字符串表(可选但强烈推荐,减少重复字符串的内存浪费)
var sharedStringPart = workbook.WorkbookPart.AddNewPart<SharedStringTablePart>();
var sharedStringTable = new SharedStringTable();
sharedStringPart.SharedStringTable = sharedStringTable;

// 创建工作表部件,处理Sheet节点(和你原来的逻辑一致)
var worksheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
var relationshipId = workbook.WorkbookPart.GetIdOfPart(worksheetPart);

uint sheetId = 1;
if (sheets.Elements<DocumentFormat.OpenXml.Spreadsheet.Sheet>().Count() > 0)
{
    sheetId = sheets.Elements<DocumentFormat.OpenXml.Spreadsheet.Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
DocumentFormat.OpenXml.Spreadsheet.Sheet sheet = new DocumentFormat.OpenXml.Spreadsheet.Sheet()
{
    Id = relationshipId,
    SheetId = sheetId,
    Name = table.TableName
};
sheets.Append(sheet);

// 用OpenXmlWriter流式写入工作表内容
using (var writer = OpenXmlWriter.Create(worksheetPart))
{
    writer.WriteStartElement(new Worksheet());
    writer.WriteStartElement(new SheetData());

    // 写入表头行
    writer.WriteStartElement(new Row());
    int iCol = 1;
    List<string> columns = new List<string>();
    foreach (System.Data.DataColumn column in table.Columns)
    {
        columns.Add(column.ColumnName);
        // 将表头字符串加入共享字符串表
        var sharedStringItem = new SharedStringItem(new Text(column.ColumnName));
        sharedStringTable.AppendChild(sharedStringItem);
        sharedStringTable.Save();

        // 写入表头单元格
        writer.WriteStartElement(new Cell());
        writer.WriteAttributeString("t", "s"); // 标记为共享字符串类型
        writer.WriteElement(new CellValue((sharedStringTable.Count() - 1).ToString())); // 引用共享字符串索引
        writer.WriteAttributeString("r", $"{ExcelColumnLetter(iCol).Trim()}1");
        writer.WriteEndElement(); // 闭合Cell标签
        iCol++;
    }
    writer.WriteEndElement(); // 闭合Row标签

    // 流式写入数据行
    int iRow = 2;
    foreach (System.Data.DataRow dsrow in table.Rows)
    {
        writer.WriteStartElement(new Row());
        iCol = 1;
        foreach (string col in columns)
        {
            string cellValue = dsrow[col].ToString();
            // 先检查共享字符串表中是否已有该字符串,避免重复存储
            var existingItem = sharedStringTable.Elements<SharedStringItem>()
                .FirstOrDefault(item => item.Text?.Text == cellValue);
            
            int stringIndex;
            if (existingItem != null)
            {
                stringIndex = sharedStringTable.IndexOf(existingItem);
            }
            else
            {
                var newItem = new SharedStringItem(new Text(cellValue));
                sharedStringTable.AppendChild(newItem);
                sharedStringTable.Save(); // 及时写入磁盘,释放内存
                stringIndex = sharedStringTable.Count() - 1;
            }

            // 写入数据单元格
            writer.WriteStartElement(new Cell());
            writer.WriteAttributeString("t", "s");
            writer.WriteElement(new CellValue(stringIndex.ToString()));
            // writer.WriteAttributeString("r", $"{ExcelColumnLetter(iCol).Trim()}{iRow}"); // 可选,Excel会自动处理单元格位置
            writer.WriteEndElement(); // 闭合Cell标签
            iCol++;
        }
        writer.WriteEndElement(); // 闭合Row标签
        iRow++;

        // 可选优化:每处理1000行就刷新一次,进一步释放内存
        if (iRow % 1000 == 0)
        {
            sharedStringTable.Save();
            writer.Flush();
        }
    }

    writer.WriteEndElement(); // 闭合SheetData标签
    writer.WriteEndElement(); // 闭合Worksheet标签
}

workbook.Close();

关键优化点说明

  • 流式写入(OpenXmlWriter):不需要把所有行对象都存在内存里,写完一行直接输出到磁盘,内存占用能降到极低。
  • 共享字符串表(SharedStringTable):重复的字符串只存一次,通过索引引用,既减少内存占用,也能缩小最终的Excel文件体积。
  • 定期刷新:每处理一定数量的行就调用Flush()和Save(),及时把内存中的数据写入磁盘,避免内存堆积。

额外建议

  • 如果你的程序是32位编译的,建议改成64位,这样程序能调用更多系统内存,进一步降低溢出概率。
  • 检查ExcelColumnLetter方法的实现,尽量避免在循环里做不必要的字符串拼接操作,保持高效。

内容的提问来源于stack exchange,提问作者K.Reed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:30:43