PhpSpreadsheet默认导出5万行以上必爆内存,因其底层为内存式写入模型:每行均clone样式、缓存字符串、维护索引,内存占用达数据体积8–12倍;CodeIgniter请求生命周期加剧该问题,且setCacheStorageMethod仅换存储位置而非跳过数组,output类还会缓冲整流,需改用XmlWriter直写OpenXML或降级CSV流式输出。

直接用 PhpSpreadsheet 默认方式导出 5 万行以上 Excel,在 CodeIgniter 里基本必爆内存——不是配置没调好,是它底层设计就不适合流式场景。
为什么 CodeIgniter + PhpSpreadsheet 一导大数据就报 Fatal error: Allowed memory size exhausted
CodeIgniter 本身不干预内存,但它的请求生命周期 + PhpSpreadsheet 默认的内存式写入模型叠加后特别危险:new Spreadsheet() 瞬间建一个空工作表,后续每写一行都往 PHP 数组里塞完整单元格对象;导到 3 万行时,$spreadsheet->getActiveSheet()->setCellValue('A1', $val) 已经在反复 clone 样式、缓存字符串、维护行列索引。实际内存占用常达数据体积的 8–12 倍。
- ThinkPHP 用户踩过的坑,CodeIgniter 一样逃不掉:别在
Controller里全程 hold 住$spreadsheet实例 - PhpSpreadsheet 的
setCacheStorageMethod()对大导出无效——缓存只是换地方存数组,不是跳过数组 - CodeIgniter 的
output->set_content_type()+output->set_output()会把整个二进制流先 buffer 在内存里再发,雪上加霜
绕过 PhpSpreadsheet 高层 API,直写 OpenXML 结构(真正流式)
核心思路:不调 setCellValue(),不碰 Worksheet 对象,用 XmlWriter 手写 [Content_Types].xml、workbook.xml、worksheets/sheet1.xml 和 sharedStrings.xml 四个关键文件,边生成边 fwrite() 到 php://output。
- 字符串必须进
sharedStrings.xml并用t="s"+s="0"引用索引,否则 xlsx 体积翻倍、解析卡死 - 数值写
t="n",布尔写t="b",日期得转 Excel 序列号再标s="1"(对应 date 样式) - 用
fopen('php://output', 'wb')开流,每写完一个<row></row>就fflush(),别等全写完 - CodeIgniter 中务必在
exit前关掉输出缓冲:ob_end_clean();,否则 nginx 可能攒满才发包
退而求其次:改用 CSV 导出(兼容性最强的降级方案)
如果业务允许 Excel 兼容格式降级为 CSV,这是最快落地、零依赖、不改架构的解法。CodeIgniter 原生支持流式输出,fputcsv() 每次只占几 KB 内存。
立即学习“PHP免费学习笔记(深入)”;
- 注意字段含逗号、换行、双引号时,必须用双引号包裹并转义内部双引号(
"a""b") - 中文导出要加 BOM 头:
\xEF\xBB\xBF,否则 Excel 打开乱码 - 不要一次性
array_chunk($data, 1000)后再循环写——chunk 本身已占内存;应从数据库游标逐条 fetch,fetch 一条写一行 - 示例关键句:
fputcsv($fp, $row, ',', '"');,其中$fp = fopen('php://output', 'w');
nginx / Apache 下容易忽略的传输层陷阱
即使 PHP 层流式写成功,Web 服务器可能把你当“小文件”缓存整段响应再发,导致用户卡在 loading、服务端内存持续涨。
- nginx 必须关掉
fastcgi_buffering on(默认是 on),加配置:fastcgi_buffering off; - Apache 的
mod_deflate如果开着,会对流式响应做全文压缩缓存,加SetEnv no-gzip 1关闭 - CodeIgniter 的
$this->output->set_header()设Content-Transfer-Encoding: binary没用,重点是禁用服务器缓冲 - 测试是否真流式:用
curl -v http://yoursite/export看响应头有没有Transfer-Encoding: chunked
真正难的不是写对第一行 XML 或 CSV,而是确保从数据库 fetch、到 PHP 写流、再到 Web 服务器吐包,中间没有任何一层偷偷缓存整段数据——任何一个环节卡住,内存就又回到原点。


















