timeout设为30秒仍卡60秒以上,是因为Hyperf的timeout仅控制单次查询执行,不覆盖DNS解析、TCP握手等底层阻塞;需同时配置PDO::MYSQL_ATTR_CONNECT_TIMEOUT(如3秒)、ATTR_TIMEOUT(如5秒)及连接池connect_timeout(如5.0秒)形成四层递进超时约束。

Hyperf 中 MySQL 连接超时问题,光配 timeout 或 connect_timeout 不够,必须同时控制连接池空闲回收、协程上下文生命周期和 PDO 底层行为。
为什么 timeout 设为 30 秒仍会卡住 60 秒以上?
Hyperf 的 timeout 配置(如 timeout: 30)只作用于单次查询执行,不包含连接建立阶段;而真实超时往往发生在 DNS 解析失败、TCP SYN 重传或服务端未响应 ACK 时。此时底层 stream_socket_client 默认阻塞约 60 秒才放弃。
- MySQL 连接池初始化时若配置了
host为域名(如mysql.example.com),DNS 解析失败会拖慢整个连接过程 -
pdo_mysql扩展不支持异步 DNS,Hyperf 协程无法拦截该阻塞点 - 连接池中
max_idle_time和min_connections设置不合理,会导致“看似有连接可用,实则全在僵死握手状态”
必须改的三个核心配置项(config/autoload/databases.php)
以下配置针对生产环境常见超时场景(网络抖动、MySQL 主从切换、中间件代理延迟):
'mysql' => [
'driver' => 'mysql',
'host' => '10.10.10.5', // ✅ 强制用 IP,绕过 DNS 阻塞
'port' => 3306,
'database' => 'test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'options' => [
PDO::ATTR_TIMEOUT => 5, // ✅ PDO 层级 socket 超时(单位:秒)
PDO::MYSQL_ATTR_CONNECT_TIMEOUT => 3, // ✅ 连接建立阶段强制限制
PDO::MYSQL_ATTR_READ_TIMEOUT => 5, // ✅ 读操作超时(含 query + fetch)
PDO::MYSQL_ATTR_WRITE_TIMEOUT => 5, // ✅ 写操作超时(含 prepare + execute)
],
'pool' => [
'min_connections' => 1,
'max_connections' => 20,
'connect_timeout' => 5.0, // ✅ 连接池获取连接的协程等待上限(秒)
'wait_timeout' => 3.0, // ✅ 获取连接时协程最多挂起时间(推荐 ≤ connect_timeout)
'heartbeat' => -1, // ✅ 关闭心跳检测(避免无效连接探测加重超时风险)
'max_idle_time' => 60, // ✅ 空闲连接最大存活秒数(建议 30~60,勿设 0 或过长)
],
],
PDO::ATTR_TIMEOUT 和 PDO::MYSQL_ATTR_CONNECT_TIMEOUT 的区别
这两个参数常被混用,但作用阶段完全不同:
-
PDO::MYSQL_ATTR_CONNECT_TIMEOUT:仅控制 TCP 连接建立(三次握手完成),不包括 DNS、SSL 握手或认证阶段 -
PDO::ATTR_TIMEOUT:控制后续所有 socket I/O 操作(query、fetch、prepare 等),但对连接建立无影响 -
PDO::MYSQL_ATTR_READ_TIMEOUT和PDO::MYSQL_ATTR_WRITE_TIMEOUT是 MySQL 特有选项,在 libmysqlclient >= 5.7.12 / mysqlnd 中才生效;Hyperf 默认用 mysqlnd,所以必须显式设置 - 若只设
ATTR_TIMEOUT为 5,但MYSQL_ATTR_CONNECT_TIMEOUT为 0(默认),连接卡在防火墙丢包时仍可能等满 60 秒
协程环境下容易忽略的「连接泄漏」触发超时
Hyperf 中一个请求对应一个协程,但若在 go 内部手动创建 PDO 实例(绕过连接池),或在异常分支中忘记 close(),会导致连接长期占用且不归还池子——后续请求排队等待,wait_timeout 触发后直接报 Connection pool is empty。
- 永远通过
Db::connection()或$this->db(注入)获取连接,不要 new PDO - 在 try/catch 外层不处理连接释放;Hyperf 连接池自动管理,但前提是没逃逸出容器生命周期
- 检查
vendor/hyperf/database/src/Connection.php中的__destruct是否被意外覆盖(某些 ORM 封装会干扰) - 用
hyperf:server:monitor查看当前活跃连接数,对比max_connections,持续接近上限即存在泄漏
真正决定超时是否“可控”的,不是某一个 timeout 值,而是连接建立、I/O、协程调度、池管理这四层超时是否形成递进约束。漏掉任意一层,都会让故障恢复时间脱离预期。



















