必须分批生成+立即释放+ZIP打包导出大数据:用主键范围分片替代LIMIT OFFSET,每批后unset对象,禁用非必要功能,验证Zip扩展与磁盘权限,记录ID范围确保数据一致。

直接导出几十万行 Excel 必爆内存,PHPExcel 和早期 PhpSpreadsheet 都不支持流式写入,save() 前所有单元格对象全驻留内存——这不是配置问题,是设计硬伤。必须放弃“查完再写”的思路,改用分批生成 + 立即释放 + ZIP 打包。
为什么 LIMIT OFFSET 分页在大数据导出中会变慢
当 OFFSET 超过 10 万,MySQL 仍要扫描前面所有行才能定位起始位置,查询耗时从毫秒级涨到秒级。更糟的是,PHP 进程在等 SQL 返回时持续占用内存,哪怕没开始写 Excel。
- 改用主键范围分片:
WHERE id > ? ORDER BY id LIMIT ?,每次取上一批最大id作为下一批起点 - 避免
SELECT *,只查导出所需字段,尤其避开TEXT、BLOB类型 - 确保
id有索引,且类型一致(比如不要用字符串字段做范围条件) - 若用时间字段分片,注意时区和重复值:同一秒内多条记录会导致漏或重,优先选自增主键
每批生成 Excel 后必须立刻释放资源
unset($objPHPExcel) 和 unset($objWriter) 不是可选项,是保命操作。残留的 Worksheet 对象会携带样式、公式、字体缓存,5 万行就能占 200MB+ 内存,循环几轮就崩。
- 每次循环内新建实例:
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); - 写完立即保存:
$writer->save($filePath),用绝对路径如/tmp/export_001.xlsx - 保存后立刻
unset($spreadsheet, $writer),别等 GC - 禁用所有非必要开销:
$spreadsheet->getProperties()->setCreator('')->setLastModifiedBy(''),关自动过滤、合并单元格、条件格式
打包 ZIP 前必须验证环境与权限
90% 的 ZIP 失败不是代码错,而是 ZipArchive 扩展没启用,或临时目录不可写,或磁盘满了。这些错误不会抛异常,只会静默失败或生成空包。
立即学习“PHP免费学习笔记(深入)”;
- 运行
php -m | grep zip确认扩展已加载 - 检查临时目录权限:
is_writable('/tmp'),并确保磁盘剩余空间 ≥ 预估总文件大小 × 1.2(Excel 压缩率差) - 用
addFile()逐个添加,不用addGlob()—— 文件名含中文或特殊字符时容易漏加 - 下载响应前调用
clearstatcache(),防止因文件系统缓存导致filesize()返回 0 - 务必用
register_shutdown_function()注册清理回调,哪怕用户中途关闭页面,也要删掉临时.xlsx文件
真正卡住人的从来不是怎么写循环,而是边界一致性——比如总行数 100007,按 10000 行/批,最后一份只有 7 行,这没问题;但若某批因 WHERE 条件漏掉 1 行,后续所有批次都会偏移,最终数据对不上。所以每批写完,建议记日志:「写入 ID 范围 [123456, 133455],共 10000 行」,导出完比对总和。



















