PhpSpreadsheet 默认内存式写入导致爆内存,因所有数据先存PHP数组再渲染;应绕过高层API,用XmlWriter流式直写OpenXML结构文件。

为什么 PhpSpreadsheet 默认写入会爆内存
因为默认用的是内存式写入:所有单元格数据先存进 PHP 数组,最后统一渲染成 Excel 二进制流。导出 10 万行 × 50 列时,内存轻松突破 512MB,Fatal error: Allowed memory size exhausted 就来了。
根本问题不在 ThinkPHP,而在 PhpSpreadsheet 的 Spreadsheet 对象本身——它不是为流式设计的。必须绕过它,直接用底层写入器。
- 别在控制器里 new
Spreadsheet(),那是内存炸弹源头 - 不要调用
$writer->save('php://output')前还保留完整工作表引用 - ThinkPHP 的
response()->download()没问题,但前提是你的内容是边生成边输出的
用 XmlWriter 手动写 .xlsx 的 sharedStrings.xml + worksheets/sheet1.xml
真正的流式导出,得跳过 PhpSpreadsheet 高层 API,直写 OpenXML 结构。核心是两个文件:sharedStrings.xml(字符串池)和 worksheets/sheet1.xml(实际数据),其他如 [Content_Types].xml、_rels/.rels 可静态复用。
关键不是“怎么写 XML”,而是“怎么让 PHP 不缓存、不拼接、不 hold 整个 XML 字符串”。答案是:fopen('php://output', 'wb') + xmlwriter_open_uri()。
立即学习“PHP免费学习笔记(深入)”;
- 用
XmlWriter的openUri('php://output'),每写一行就 flush 到浏览器 - 字符串统一进
sharedStrings.xml,避免重复写相同文本(否则 xlsx 体积暴涨) - 单元格坐标用
PHPExcel_Cell::stringFromColumnIndex()类似逻辑手动算,别依赖Coordinate类 - 数值类型要区分:
t="s"(字符串索引)、t="n"(数字)、s="1"(样式索引)
ThinkPHP 控制器里怎么安全响应大文件流
不能走 return $this->fetch() 或普通 Response 对象,它们会缓冲输出。必须手动控制 header 和输出流,并禁用 ThinkPHP 的自动 content-length 注入。
- 开头加
ob_end_clean()清掉可能已开启的输出缓冲 - 设 header:
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') - 设 header:
header('Content-Disposition: attachment;filename="export.xlsx"') - 关掉 ThinkPHP 的自动响应包装:
response()->send()后立即exit,别让它再走后续中间件 - 如果用了 nginx,确认没开
fastcgi_buffering on,否则它会等整个流结束才发包
PhpSpreadsheet 的 MemoryCache 和 IGlobalFunctions 陷阱
有人试过设 Settings::setCacheStorageMethod(Settings::CACHE_TO_DISCIS),结果发现没用——因为流式写入根本不走 cell 缓存逻辑;还有人试图 hook PHPExcel_Shared_String,但新版 PhpSpreadsheet 已删掉这个类。
真正卡住人的点是:你以为在“优化”,其实还在用错的抽象层。只要还调 $spreadsheet->getActiveSheet()->setCellValue(...),就注定失败。
-
setCacheStorageMethod()只影响读取大文件,对写入无效 -
setCellCacheEnabled(false)是个伪开关,写入路径压根不查它 - 别碰
IOFactory::createWriter($spreadsheet, 'Xlsx'),它内部强制加载全部 sheet 数据 - 想省事?用
spout库替代——它原生流式,API 简单,但只支持 xlsx/csv,不支持公式/样式
流式导出真正的复杂点不在代码量,而在于你得亲手构造 OpenXML 的碎片结构,并确保 zip 包内各文件顺序、路径、MIME 关系完全正确。少一个 xl/_rels/workbook.xml.rels,Excel 就打不开;sharedStrings.xml 里多一个未闭合标签,Office 就报“发现错误并尝试恢复”。这些细节没法靠框架兜底,只能硬核验证。


















