用slowlog快速捕获超时thinkphp请求:在宝塔面板中启用slowlog=/www/wwwlogs/php_slow.log与request_slowlog_timeout=2s,开启catch_workers_output=yes后重启php,复现问题时tail -f日志即可定位卡住的文件及行号。

ThinkPHP死循环不能靠“看代码猜”,得靠日志、堆栈和进程三路交叉验证。光改 build.php 或硬加 echo 只能临时定位单点,线上持续卡住的脚本必须从 PHP-FPM 进程层反向抓取。
怎么用 slowlog 快速捕获超时的 ThinkPHP 请求
ThinkPHP 的死循环往往发生在控制器方法、模型查询回调或中间件里,request_slowlog_timeout 是最轻量有效的被动捕获手段:
- 宝塔面板中打开对应站点的 PHP 配置,在
php-fpm.conf或www.conf里确认已启用:slowlog = /www/wwwlogs/php_slow.log和request_slowlog_timeout = 2s(别设成 0.5s,ThinkPHP 自身初始化就可能耗时) -
catch_workers_output = yes必须开启,否则error_log()或trigger_error()不会落进 slowlog - 重启 PHP 后,等异常请求复现,直接
tail -f /www/wwwlogs/php_slow.log,你会看到类似这样的记录:[20-Apr-2026 07:12:33] [pool www] pid 12345<br>script_filename = /www/wwwroot/app/public/index.php<br>[0x00007f8b1c0a9e50] handle() /www/wwwroot/app/app/middleware/CheckAuth.php:42
- 注意看最后一行的文件路径和行号——这大概率就是死循环入口,不是报错行,是“卡住”的行
为什么 think\exception\ErrorException 不显示堆栈
这不是 ThinkPHP Bug,而是错误处理器被绕过了。常见于以下场景:
- 在
public/index.php之外直接执行脚本(比如 CLI 模式跑定时任务),没走App::run()流程,think\ExceptionHandle根本没注册 - 自定义了
set_error_handler()但没调用parent::report(),导致 ThinkPHP 的错误转异常逻辑失效 -
error_reporting被设为0或E_ALL & ~E_NOTICE,而死循环触发的是E_WARNING级别(比如数组越界、资源耗尽) - 检查
config/app.php中'app_debug' => true是否生效,false 时部分错误会被静默吞掉
strace + ps 定位正在跑飞的 ThinkPHP 进程
当 slowlog 没触发(比如循环体里全是内存计算、没 IO)、CPU 却飙到 100%,就得上系统级追踪:
- 先用
ps aux --sort=-pcpu | grep 'php-fpm' | head -10找出 CPU 最高的几个php-fpmworker PID - 对每个可疑 PID 执行:
strace -p <pid> -e trace=epoll_wait,read,write -s 64 -c 2>&1 | head -20</pid>,如果输出全是重复的epoll_wait返回 0,说明它卡在事件循环里;如果全是read(0, ...)就可能是 stdin 阻塞或配置读取失败 - 更进一步,用
cat /proc/<pid>/stack</pid>查内核态堆栈,若看到大量zif_array_merge或zif_count循环调用,基本可断定是模型关联查询嵌套过深或递归未设终止条件 - 别急着
kill -9,先lsof -p <pid> | grep REG</pid>看它当前打开的 PHP 文件,再结合 slowlog 时间戳比对,确认是不是同一个请求
ThinkPHP 关联查询引发的隐性死循环
这不是语法错误,是逻辑陷阱。典型案例如下:
- 模型定义了
hasOne关系,但关联字段值为空或为 0,且没设->where('xxx > 0'),导致查询返回空结果集后,业务代码又误判为“需重试”,进 while 循环 - 使用
with(['relationA', 'relationB'])时,两个关联模型互相belongsTo对方,且没有设置lazy或defer,加载时触发无限递归查询 -
scope方法里写了$query->where(...)->where(...),但其中某个条件始终为真(如status = status拼错成变量未替换),WHERE 条件恒成立 - 在
foreach遍历集合时,循环体内又调用了$model->save(),而该模型的save触发了 observer 或 event,再次进入同个 foreach —— 这种要查app/observe和app/event目录
真正难排查的不是语法级死循环,而是由 ThinkPHP 生命周期、关系加载机制和运行时环境共同催生的“条件型卡顿”。slowlog 是起点,strace 是验证,而 /proc/<pid>/stack</pid> 和 lsof 才是最终确认执行现场的关键证据。别信“应该不会”,要看 cat 出来的实时内容。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











