PHP实时输出内存暴涨主因是ob_start()后未及时ob_flush()和flush()致缓存堆积,或file_get_contents()等整读大文件;Nginx fastcgi_buffering默认开启会拦截flush,须关闭。

PHP实时输出时内存暴涨的典型原因
开启 ob_start() 后又用 echo 频繁输出,但没及时调用 ob_flush() 和 flush(),会导致所有输出缓存在内存里,直到脚本结束才释放——这是最常见、最隐蔽的内存堆积点。
另一个高发场景是:用 file_get_contents() 或 curl_exec() 读取大文件/响应体后直接 echo,没做流式处理,整块内容全进内存。
- Web 服务器(如 Nginx)默认启用 gzip 压缩,会拦截并缓冲 PHP 的
flush()输出,导致 PHP 层以为已刷出,实际还在服务端积压 -
output_buffering设置为On或数值(如4096)时,即使手动 flush,也会被 PHP 内部 buffer 拦截一次 - CLI 模式下
flush()无效,ob_flush()也无意义——实时输出在 CLI 里靠的是echo+fflush(STDOUT)
用 fopen("php://output") 替代 echo 流式写入
绕过输出缓冲层最稳妥的方式,是把内容直接写到原始输出流。尤其适合导出 CSV、日志流、或长轮询响应。
$fp = fopen("php://output", "w");
// 每生成一行就立刻写出,不累积
fputcsv($fp, ["id", "name", "time"]);
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
fputcsv($fp, $row);
// 强制刷新底层流(对 CLI 有效,Web 下配合 server 配置)
fflush($fp);
}
fclose($fp);
- 比
echo+ob_flush()+flush()更底层,不受output_buffering影响 - 注意:Nginx 仍可能缓冲,需关掉
fastcgi_buffering off;(仅限该 location) - 不能和
ob_start()混用,否则fopen("php://output")写入会被 buffer 拦截
禁用 PHP 输出缓冲并校验生效状态
光在代码里调用 ob_end_flush() 不等于清除了所有缓冲层级。必须从配置源头切断。
立即学习“PHP免费学习笔记(深入)”;
检查并修改以下三处:
-
php.ini 中设
output_buffering = Off(不是0,0在某些 PHP 版本下仍启用默认 buffer) - 运行时强制关闭:
if (ob_get_level()) { ob_end_clean(); }—— 放在脚本最开头 - 确认 Web 服务器未注入额外 buffer:Apache 加
SetEnv no-gzip 1;Nginx 在 location 块加gzip off;和fastcgi_buffering off;
验证是否生效:执行 var_dump(ob_get_level()); 应返回 int(0);再用 echo "x"; flush(); 配合 curl -N 测试是否秒出。
大文件下载别用 readfile(),改用 stream_copy_to_stream()
readfile() 会把整个文件读进内存再吐出,100MB 文件就吃掉 100MB 内存;而流式复制只维持几 KB 缓冲区。
$src = fopen("/path/to/big.zip", "rb");
$dst = fopen("php://output", "wb");
// 默认 chunk size 是 8192 字节,内存占用恒定
stream_copy_to_stream($src, $dst);
fclose($src);
fclose($dst);
- 务必用
"rb"和"wb"模式,避免 Windows 下换行符干扰二进制流 - 如果需要断点续传,得自己解析
Range请求头,用fseek()+fread()分段读,不能依赖stream_copy_to_stream() - PHP 8.1+ 可用
stream_copy_to_stream($src, $dst, $maxlen)控制总拷贝量,防止单次传输过大
fastcgi_buffering——它默认开,且优先级高于 PHP 的 flush 控制,不关掉的话,前面所有 PHP 层优化都白做。











