set_time_limit(0)在ThinkPHP中常失效,因Web服务器(Nginx/Apache)和PHP-FPM的超时配置(如fastcgi_read_timeout、request_terminate_timeout)才是实际瓶颈,且ThinkPHP的输出缓冲与Response机制会干扰流式传输。

为什么 set_time_limit(0) 在 ThinkPHP 下经常失效
PHP 的 set_time_limit(0) 确实能取消脚本执行时间限制,但 ThinkPHP 默认启用了输出缓冲(ob_start)和响应拦截机制,一旦响应体过大或输出不及时,底层 SAPI(如 Apache 的 mod_php 或 FPM)仍会触发超时——尤其是 Nginx + PHP-FPM 组合下,fastcgi_read_timeout 和 request_terminate_timeout 才是真正掐断连接的开关。
常见错误现象:504 Gateway Timeout 或 Connection reset by peer,而 PHP 错误日志里却看不到超时记录;set_time_limit(0) 放在控制器开头也没用。
- ThinkPHP 的
Response类默认调用send()时会一次性写入全部内容到缓冲区,大文件直接readfile()会导致内存暴涨、缓冲阻塞 - FPM 的
request_terminate_timeout是硬性限制,set_time_limit完全无法绕过它 - Apache 的
TimeOut指令或 Nginx 的fastcgi_read_timeout必须同步调大,否则 PHP 层还没超时,网关已断连
用 fpassthru() + 分块读取替代 readfile()
ThinkPHP 原生 Response::download() 内部用的是 readfile(),对几百 MB 以上文件极易卡死。必须手动接管输出流,用 fopen + fread + fpassthru 控制每次读取大小,并主动 flush()。
使用场景:导出日志、备份 SQL、视频片段下载等无法预估体积或必须流式传输的场景。
立即学习“PHP免费学习笔记(深入)”;
- 务必关闭所有输出缓冲:
if (ob_get_level()) ob_end_clean(); - 设置合适的分块大小(通常
8192或65536字节),太小增加系统调用开销,太大仍可能触发内存限制 - 每次
fread()后立即echo并调用flush(),避免被中间件/代理缓存整块响应 - 必须在
header()发送后、读取前调用ignore_user_abort(true),防止用户中途关闭页面导致进程终止
示例关键片段:
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="bigfile.zip"');
header('Content-Transfer-Encoding: binary');
ignore_user_abort(true);
if (ob_get_level()) ob_end_clean();
$fp = fopen('/path/to/bigfile.zip', 'rb');
while (!feof($fp) && connection_status() === CONNECTION_NORMAL) {
echo fread($fp, 8192);
flush();
}
fclose($fp);
ThinkPHP 6+ 中如何安全集成流式下载逻辑
直接在控制器里写裸 echo + flush() 会破坏 ThinkPHP 的响应生命周期,比如中间件未执行完、日志未落盘、异常未捕获。正确做法是继承 think\Response,重写 send() 方法,把流式逻辑封装进响应对象。
参数差异:Response::create() 的第三个参数是类型,不能传 'download'(那是快捷包装,不支持流式),得传 'html' 或空字符串,再手动控制 header 和输出。
- 不要用
$this->success()或return download(...)这类快捷方法 - 禁用
Content-Length头(除非你能精确计算文件大小且客户端支持 chunked),否则部分代理会等待完整长度才开始传输 - 若启用 Gzip 压缩(
zlib.output_compression),必须关闭,否则flush()失效 - 确保
output_buffering = Off或设为0(php.ini),否则flush()只清 PHP 缓冲,不触达 Web 服务器
哪些配置项你一定得改,光改 PHP 不行
只调 PHP 的 max_execution_time 和 set_time_limit 是徒劳的。Web 服务器和 PHP-FPM 的超时策略才是实际瓶颈。
常见错误现象:本地测试 OK,上线就 504;或下载进行到 60 秒左右必然中断。
- Nginx:
fastcgi_read_timeout 300;(需大于预期最大下载耗时) - PHP-FPM:
request_terminate_timeout = 300s(注意不是max_execution_time) - Apache:
Timeout 300(mod_php)或ProxyTimeout 300(mod_proxy_fcgi) - PHP:
default_socket_timeout = 300(影响 cURL 等,非直接相关但常被忽略)
这些值必须协同调整,且建议留出 20% 余量——网络抖动、磁盘 I/O 延迟都可能导致临界超时。
流式下载真正的复杂点不在代码,而在整个请求链路上每个环节的缓冲与超时策略是否对齐。少改一处,就可能在某个环境里静默失败。



















