PDO::ATTR_TIMEOUT仅控制连接建立超时,对SQL执行无作用;应通过MySQL的MAX_EXECUTION_TIME、PHP-FPM slowlog及检查wait_timeout/中间件超时三方面协同解决慢查询。

PHP 本身没有直接的「PDO 查询执行超时」机制,PDO::ATTR_TIMEOUT 只控制连接建立阶段,对 query() 或 execute() 阶段的 SQL 执行不生效。真正卡住 Web 进程的,往往是没加 LIMIT 的全表扫描、缺失索引的 WHERE 条件、或被锁住的事务——这些必须靠组合手段提前拦截,而不是指望 PDO 自动中断。
为什么 PDO::ATTR_TIMEOUT 对慢查询无效
PDO::ATTR_TIMEOUT 是 PDO 层面对底层 socket 连接握手的等待上限,仅作用于 new PDO() 阶段;一旦连接成功,后续所有 query()、execute() 调用都交由 MySQL 服务端执行,PDO 完全不参与超时判定。实测中即使设为 PDO::ATTR_TIMEOUT => 1,一个 SELECT SLEEP(10) 仍会完整执行 10 秒才返回。
常见误判现象:
- Web 页面卡死 20 秒,日志里却没报任何 PDO 异常 —— 因为连接早建好了,只是 SQL 在 MySQL 里慢慢跑
-
set_time_limit(5)在 CLI 下有效,但在 PHP-FPM 中可能被request_terminate_timeout(如 Nginx fastcgi_read_timeout)截断,且无法精确到单条查询
MySQL 层强制限制单条查询执行时间
从源头掐断慢查询最可靠的方式,是让 MySQL 自己拒绝执行过长的语句。PHP 无需改代码,只需在查询前下发指令:
立即学习“PHP免费学习笔记(深入)”;
$pdo->exec("SET SESSION MAX_EXECUTION_TIME = 3000"); // 单位毫秒,MySQL 5.7.8+
$pdo->query("SELECT * FROM huge_table WHERE unindexed_col = 'xxx'");
注意点:
- 该设置只对当前连接 session 生效,需每次连接后重置(可封装进连接初始化逻辑)
- 低于 MySQL 5.7.8 的版本不支持,替代方案是用
KILL QUERY {connection_id}配合外部监控,但实时性差 - 触发时 MySQL 返回错误:
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded,PHP 会抛出PDOException - 它不影响连接建立、网络传输等环节,只管 SQL 计算耗时
用 PHP-FPM slowlog 快速定位阻塞点
当慢查询已发生并拖垮了整个请求,slowlog 是唯一能告诉你「到底卡在哪一行」的工具。它不依赖代码修改,纯配置生效:
在 PHP-FPM Pool 配置(如 /etc/php/8.2/fpm/pool.d/www.conf)中添加:
request_slowlog_timeout = 2s slowlog = /var/log/php-fpm/www-slow.log
重启后,若某次请求因 sleep(5) 或未设超时的 file_get_contents() 卡住,日志会直接打出调用栈:
[20-Apr-2026 02:38:12] [pool www] pid 9876 script_filename = /srv/app/api/user.php [0x00007f8b1c0a1234] query() /srv/app/db/DB.php:67 [0x00007f8b1c0a1256] fetchUser() /srv/app/api/user.php:22
关键细节:
- 路径
/var/log/php-fpm/必须由www-data用户可写,否则日志静默失效 - 阈值别一上来就设
100ms,先用2s观察,再逐步收紧 - 它只对 PHP-FPM Web 请求有效,CLI 脚本需另配
pcntl_alarm或stream_select
真正需要你动手检查的三个地方
别只盯着 PDO 参数。慢查询阻塞 Web 进程,90% 的根因藏在这三处:
- MySQL 的
wait_timeout和中间件(Nginx、ALB、HAProxy)的 idle timeout 不一致,导致连接池复用时偶发MySQL server has gone away,重连过程隐形拉长响应 - SQL 里用了
LIKE '%xxx'、ORDER BY RAND()、或没走索引的JOIN,EXPLAIN一看type=ALL就知道要命 - PHP-FPM 的
pm.max_children设置过小,1 个慢查询占满全部 worker,后续请求直接排队等死
查 wait_timeout:运行 SHOW VARIABLES LIKE 'wait_timeout';,生产环境建议设为 300(5 分钟),别用默认 8 小时;查中间件超时,翻 Nginx 的 fastcgi_read_timeout 或 ALB 的 Idle Timeout 设置——它们往往比 PHP 层更早动手 kill 连接。



















