原生PDO在Swoole协程中不安全,因其底层为阻塞socket,无法被协程调度器劫持,导致协程卡死;必须使用Swoole\Database\PDOPool等协程封装层,通过连接池获取协程安全的PDO实例,并显式归还连接以避免泄漏。

原生 PDO 在 Swoole 协程中直接使用会出问题——它不是协程安全的,哪怕开了 Swoole\Runtime::enableCoroutine(),底层仍是同步阻塞调用,协程会被卡住,导致整个进程假死或并发能力归零。
为什么原生 PDO 不能直接在协程里用
PHP 的 PDO 扩展本身不感知协程,它的 MySQL 连接(如 mysqlnd)底层是阻塞 socket,Swoole 的协程调度器无法劫持其 I/O。即使你用 go() 包裹 $pdo->query(),该调用仍会同步等待网络响应,期间协程无法让出,其他协程全部被饿死。
- 现象:高并发下 QPS 不升反降,
co::stats()->coroutine_num持续飙高但无实际并发收益 - 错误日志里看不到报错,但
strace能看到大量recvfrom阻塞 - 和
file_get_contents()不同,PDO 不在 Swoole 默认协程化白名单里
必须用 Swoole\Database\PDOPool 或协程适配驱动
真正能走协程的 PDO 查询,得靠 Swoole 官方提供的协程封装层,核心是 Swoole\Database\PDOPool —— 它内部用的是 Swoole\Coroutine\MySQL 底层,所有 I/O 全部协程化,且自带连接复用和自动重连。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 不要自己 new
PDO实例后丢进go(),那只是“伪协程” - 连接池初始化时传入的是
Swoole\Database\PDOConfig,不是传统 DSN 字符串 -
$pool->get()返回的PDO对象是协程安全的,prepare()/execute()全部非阻塞 - 用完必须调
$pool->put($pdo)归还,否则连接泄漏,池子很快耗尽
示例关键片段:
$config = (new \Swoole\Database\PDOConfig())
->withHost('127.0.0.1')
->withPort(3306)
->withDbName('test')
->withUsername('root')
->withPassword('123456');
$pool = new \Swoole\Database\PDOPool($config, 4); // 最大4个连接
go(function () use ($pool) {
$pdo = $pool->get();
$stmt = $pdo->prepare('SELECT id FROM user WHERE id > ?');
$stmt->execute([100]);
$rows = $stmt->fetchAll();
$pool->put($pdo); // 必须归还
});
Hyperf/Laravel 等框架里怎么处理
如果你没直接写 Swoole Server,而是用 Hyperf,它已内置 hyperf/database,底层自动桥接 PDOPool;Laravel 则不行——官方 illuminate/database 仍是同步 PDO,硬要在 Laravel 中用协程,只能把查询逻辑拆到 Command 里跑独立协程,绝不能放在 HTTP Request 生命周期内。
- Hyperf 中只需配置
db.default.driver=pdo_mysql,框架自动用池 - Laravel 的
DB::select()在 Swoole onRequest 回调里调用 = 直接阻塞协程 - Hyperf 的
Db::select()是协程安全的,因为它走的是自己的连接池抽象层
最容易被忽略的一点:事务必须在单个 $pdo 实例内完成,不能 get 一次、执行 SQL、put 回去、再 get 一次继续 commit —— 连接池里的每个连接是独立上下文,跨连接的事务无效,也不会报错,只会静默失败。

















