导出百万级数据内存爆掉的主因是全量加载而非php性能不足,应采用流式处理:逐行查询、即时写入、避免中间数组、数据库层去重排序、显式释放变量并调用gc_collect_cycles()。

导出时内存爆掉,根本不是PHP不行
PHP 8.2 导出百万级数据卡死、报 Allowed memory size exhausted,90% 的情况不是 PHP 本身扛不住,而是你让整个数据集一次性进内存了。比如用 file_get_contents 读 CSV、用 mysqli_fetch_all 拿全量结果、或把所有记录塞进一个大数组再统一写文件——这些操作在 50 万行以上基本必崩,哪怕 memory_limit 调到 512M 也没用。
用生成器边读边写,内存恒定在几 MB
关键不是“怎么导出”,而是“怎么不加载”。数据库导出要绕过 fetch_all,CSV 导出要绕过 file_get_contents。核心是流式处理:
- 数据库查询用
mysqli_use_result()或 PDO 的PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,避免客户端缓存全部结果集 - 逐行 fetch:用
mysqli_fetch_assoc($result)或$stmt->fetch(),处理完立刻丢弃该行引用 - 写文件用
fopen(..., 'w')+fputcsv(),每处理一行就写一行,不缓存整张表 - 如果用生成器封装,记得加类型声明:
function exportRows(string $sql): \Generator,PHP 8.2 会更早释放内部资源
别让数组成内存黑洞
导出逻辑里最容易偷偷吃内存的是中间数组。比如你为了去重建了个 $seenIds = [],或者为了排序先 collect 全部数据再 usort——这两步在百万级下直接干掉几百 MB。真实项目中更稳妥的做法是:
- 去重改用数据库层完成:
SELECT DISTINCT ...或GROUP BY,别拉到 PHP 做 - 排序交给数据库:
ORDER BY created_at DESC,除非业务强制要求 PHP 端动态规则 - 真要 PHP 去重二维数组,用
array_unique($rows, SORT_REGULAR),它不序列化,比array_map('serialize', ...)省至少 3 倍内存 - 处理完单行后,显式
unset($row),尤其当$row含大字段(如 JSON blob、base64 图片)时
导出结束必须手动触发 GC
即使你做了上面所有事,PHP 的垃圾回收器(GC)也不会立刻释放循环引用或临时对象。导出脚本末尾容易被忽略的一句是:
gc_collect_cycles();
它强制扫描并回收可销毁对象。特别在 CLI 模式下(比如用 php export.php 跑定时任务),这句能让内存回落更干净。不加它,多次运行后内存占用会缓慢爬升——你以为是泄漏,其实是 GC 懒了。
最麻烦的不是怎么写,而是你得确认每一行数据只在内存里活那么一瞬;多留 0.1 秒,百万行就多占几十 MB。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











