百万级excel导出必现oom或超时,根本原因是框架+excel库全量加载数据;应优先用csv流式导出,配合db::cursor()、fputcsv()和禁用输出缓冲;必须.xlsx时选用php-xlswriter或spout;复杂关联场景需分片查库+字典预加载;绕过框架响应链手动控制输出。

直接用 ThinkPHP6.x 默认方式导出百万级 Excel,基本必现内存溢出(OOM)或超时。根本原因不是数据多,而是框架+Excel库默认把全部数据加载进内存再生成文件。解决核心就三点:不全量加载、不缓存响应、不依赖笨重格式。
优先用 CSV 替代 Excel
绝大多数业务导出其实不需要样式、公式、多 Sheet——那就不该用 .xlsx。CSV 是纯文本,PHP 原生 fputcsv() 配合 php://output 可做到边查边写、内存恒定在几百 KB。
- 用 Db::cursor() 获取 PDOStatement,逐行 fetch,避免 chunkById() 的 COUNT(*) 和 offset 性能陷阱
- 表头和每行数据都用 fputcsv($fp, $row) 写入,写完立即 fflush($fp)
- 禁用 PHP 输出缓冲:ini_set('output_buffering', 'Off') 和 ini_set('zlib.output_compression', 'Off')
- 响应头必须前置设置:Content-Type: text/csv、Content-Disposition: attachment; filename="data.csv"
真流式 Excel:EasyExcel 或 SXSSFWorkbook
如果必须输出 .xlsx(比如要颜色、冻结窗格、数字格式),就放弃 phpExcel/PHPExcel,改用真正流式引擎:
- EasyExcel(Java 服务端常用,PHP 可通过 API 调用或改用其思想):但 PHP 生态更推荐原生兼容方案
- PHP-XLSWriter(强烈推荐):轻量、无依赖、纯写入、不读取、支持百万行,内存占用极低,官网已明确支持 TP6
- 若坚持用 Apache POI 思路,PHP 端可用 Spout 库(非 POI,但同为流式),它只支持 .xlsx/.ods,不支持 .xls,且不解析样式
分片 + 合并:适合复杂关联场景
当导出需 JOIN 多张字典表、且无法预加载时,全量流式会反复查库拖慢速度。这时可拆解为:
- 先将高频字典表(如地区、状态、分类)一次性查出,存入 Redis Hash 或本地静态数组
- 主表用游标分页(WHERE id > ? LIMIT 1000),每批查 1000 行,查完立刻查字典补全字段,写入临时 CSV 文件
- 所有临时文件生成后,用 file_get_contents + file_put_contents(FILE_APPEND) 合并(首行跳过)
- 最后用 ZipArchive 打包成 ZIP 下载,避免浏览器对超大单文件的限制
绕过框架响应链,手动控制输出
ThinkPHP 的 return response()->stream() 在高版本中仍可能被中间件拦截缓冲。最稳妥做法是:
- 控制器方法末尾不用 return,改用 exit() 或 die()
- 手动 fopen('php://output', 'wb'),全程自己管理文件句柄
- Nginx 需调大 proxy_read_timeout 600(10 分钟),PHP-FPM 设置 request_terminate_timeout=0
- 禁用所有视图渲染、日志记录、性能监控中间件(导出接口单独路由+中间件白名单)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











