hyperf中慢查询导致连接池卡死,因swoole协程mysql客户端为同步i/o封装,sql无索引、锁等待或长事务时协程永久阻塞在recvfrom,worker停摆;须加三类索引、显式设pdo超时、消费全部结果集、清理sleep连接。

Hyperf里慢查询为什么直接卡死连接池
不是“慢”,是“卡死”——Swoole协程MySQL客户端本质是同步I/O封装,发起SELECT后协程挂起等待socket就绪;一旦SQL没走索引、全表扫描或锁等待超时,MySQL不返回结果,协程就永远等在recvfrom调用上,整个Worker进程停摆,其他几十个协程全部被阻塞。
现象很典型:swoole_server->stats()显示coroutine_num飙升但request_count几乎为0;strace -p [pid] -e trace=recvfrom能看到大量阻塞调用。
-
DB::timeout(5)对原生Db::select()无效,只作用于Query Builder层 - Hyperf默认未启用MySQL驱动级超时,依赖
connect_timeout和read_timeout,但常被忽略 - 慢查询+长事务+未提交,会让连接长期处于
Query或Locked状态,无法归还给池
必须加的三类索引,否则with()也救不了
Hyperf中with()预加载不能掩盖底层SQL缺陷。若关联字段没索引,生成的IN子查询或JOIN反而触发全表扫描,EXPLAIN FORMAT=TREE会显示rows远超预期、出现Using temporary。
-
belongsTo关系:外键字段(如user_id)必须建B+树索引,且WHERE条件不能包裹函数(如WHERE DATE(created_at) = '2026-05-25') -
hasMany分页:复合索引要覆盖ORDER BY + LIMIT,例如INDEX (status, created_at)比单列created_at高效得多 - 多层嵌套
with(['user', 'user.profile']):确保profile.user_id有索引,否则第二层JOIN直接全表扫
Java式try-with-resources在Hyperf里怎么写
Hyperf没有try-with-resources语法,但必须模拟等效行为:每个Db::query()或Db::statement()调用后,显式释放资源并设超时边界。漏掉任何一步,连接就卡住。
- 用
Db::connection()->getPdo()->setAttribute(PDO::ATTR_TIMEOUT, 5)设PDO级读超时(注意:需在每次获取连接后设置) - 存储过程调用必须消费全部结果集:
do { $rs = $stmt->getResultSet(); if ($rs) { while ($rs->fetch()) {} } } while ($stmt->nextRowset()); - 禁用所有隐性同步IO:日志换
Hyperf\Logger\Handler\StdoutHandler,关掉opcache.file_cache,否则协程照样被拖住
连接池耗尽时先杀什么连接
别急着调max_connections——它只是计数器,不解决根本问题。优先清理那些“连着却不干活”的连接,尤其是Command = 'Sleep'且Time > 300秒的线程,它们大概率是应用拿了连接没关,或存储过程返回多个结果集后没消费完。
- 查
information_schema.innodb_trx确认是否真有未提交事务,再决定是否KILL - 用
SHOW PROCESSLIST过滤Info LIKE 'CALL%'定位拖住连接的存储过程 - 设置
wait_timeout = 60(非interactive_timeout),让空闲连接60秒自动断开 - HikariCP侧配
leakDetectionThreshold = 60000,日志里直接打出哪行PHP代码借了连接没还
连接池耗尽从来不是单点故障,而是慢SQL、未关闭结果集、隐性IO、索引缺失四者叠加的结果。盯住SHOW PROCESSLIST里Time列和Info字段,比调参数管用十倍。











