Hyperf中MySQL连接必须使用hyperf/db封装的协程客户端,禁用PDO和Swoole\MySQL原生类,因其非协程安全;需通过DI容器注入Connection实例,由连接池统一管理物理连接,配置driver为mysql并启用pool参数,避免手动new或绕过池机制。

Hyperf 中 MySQL 连接必须用协程客户端,不能复用 Swoole MySQL 或 PDO
Hyperf 默认不支持传统 PDO 或 Swoole\MySQL,因为它们不是协程安全的。一旦在协程中混用,会导致连接错乱、数据污染甚至进程崩溃。必须使用 hyperf/db 封装的 Db 组件,底层基于 co-mysql(即 Swoole\Coroutine\MySQL)。
常见错误现象:SQLSTATE[HY000] [2002] Connection refused 或查询返回空结果但无报错——本质是连接被其他协程抢占或提前关闭。
- 确保已安装
hyperf/db和hyperf/database(v3.x 推荐用hyperf/db单组件) - 配置文件
config/autoload/databases.php中,driver必须为mysql,且pool配置需显式启用协程支持 - 不要手动 new
Swoole\Coroutine\MySQL—— 它无法被 Hyperf 的连接池管理,会绕过超时、健康检查等机制
单例连接实际是连接池 + DI 容器自动注入,不是手写单例类
Hyperf 里没有“手写 MySQL 单例”的必要,也不推荐。所谓“单例”,是指通过容器获取的 Hyperf\Db\Connection 实例,在一次请求生命周期内由 DI 自动复用;而底层连接由连接池按需分配、回收。
直接调用 make 或构造函数注入即可,容器会保证每次获取的是逻辑一致的连接对象(非物理连接):
// 在 Command 或 Service 中
use Hyperf\Db\Connection;
<p>class UserService
{
public function __construct(private Connection $connection) {}</p><pre class="brush:php;toolbar:false;">public function getUser($id)
{
return $this->connection->select('SELECT * FROM users WHERE id = ?', [$id]);
}}
- 不要用
new Connection(...)—— 缺失连接池、配置解析、异常重试等能力 - 若需跨协程共享同一物理连接(极少见),应使用
Connection::getPdo()并配合defer手动释放,但会破坏连接池语义,慎用 - 连接池大小(
min_connections/max_connections)影响并发承载力,小项目设10/20足够,高并发需压测调整
完整可运行配置与初始化代码(含常见坑点)
以下是最简可用配置,放在 config/autoload/databases.php:
return [
'default' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => (int) env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'test'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'fetch_mode' => PDO::FETCH_ASSOC,
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60.0,
],
'options' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
];-
heartbeat => -1表示禁用心跳检测(避免低版本 MySQL 报Unknown command);如需开启,设为正整数秒 -
PDO::ATTR_EMULATE_PREPARES => false是关键,否则预处理语句走本地模拟,失去协程 MySQL 的参数绑定优势 - 环境变量未设置时,
env()返回null,导致连接失败;务必确认.env存在且格式正确(无空格、无引号包裹)
验证连接是否真正协程化:用 co::sleep 模拟 IO 等待
最直接的验证方式,是在查询前后插入 co::sleep(1),观察是否阻塞整个进程:
public function testConcurrency()
{
go(function () {
$start = microtime(true);
$this->connection->select('SELECT SLEEP(2)');
var_dump('query done in ' . (microtime(true) - $start));
});
<pre class="brush:php;toolbar:false;">go(function () {
co::sleep(0.5);
var_dump('non-blocked after 0.5s');
});
// 输出应为:先打印 'non-blocked...',1.5s 后再打印 'query done...'}
- 如果第二条
var_dump在 2.5 秒后才输出,说明连接未走协程路径(比如误用了 PDO) - 生产环境别用
SLEEP(),可用真实慢查询或co::sleep+SELECT 1组合验证 - 连接池耗尽时会卡在
wait_timeout,此时日志会出现Wait timeout for connection from pool,需调大max_connections或优化 SQL
真正要注意的不是“怎么写单例”,而是别让连接脱离连接池管控——哪怕只漏掉一次手动 new,都可能在高并发下引发雪崩。配置和注入方式比代码本身更关键。



















