swoole常驻内存下必须配置数据库连接池以复用连接,否则协程并发新建tcp连接将触发mysql“too many connections”;需升级swoole≥4.8.0、think-swoole≥3.1.0,database.php中禁用pdo驱动与读写分离,显式调用db::pool()才能生效。

在Swoole常驻内存环境下,数据库连接复用必须打破PHP-FPM“一请求一连接”惯性,否则每个协程都新建TCP连接,MySQL很快报Too many connections;不配置连接池,复用就只是空谈。
确认运行环境与扩展版本
执行php --ri swoole,确认输出中包含version => 4.8.0或更高;低于此版本无法协程化PDO/MySQLi,强行启用会退化为同步阻塞。
运行composer show topthink/think-swoole,确保版本≥3.1.0;旧版不支持DatabasePool自动协程ID隔离,连接污染无法避免。
【hostname、username、password必须明文写死在config/database.php中,不能调用env()读取】——协程上下文切换时环境变量不可靠,会导致连接初始化失败静默降级。
ThinkPHP项目:四步硬配database.php
打开config/database.php,定位到主数据库配置块(通常是'default' => 'mysql'下的'mysql'子数组):
- 将
'type' => 'pdo_mysql'改为'type' => 'mysql':强制走Swoole原生协程MySQL客户端,PDO驱动不兼容协程事务状态管理; - 将
'deploy' => 1(或未定义)改为'deploy' => 0:关闭读写分离,否则Connection类内部会绕过协程驱动,偷偷创建同步PDO连接; - 在
'params'数组中添加PDO::ATTR_EMULATE_PREPARES => false:禁用预处理模拟,否则SQL被PHP同步拼接,协程化失效; - 删除所有
PDO::ATTR_PERSISTENT => true相关配置:它在Swoole中完全无效,且可能干扰连接池健康检测。
接管Db门面:绑定DatabasePool实例
在app/provider.php末尾追加:
use think\swoole\pool\DatabasePool;
use think\swoole\pool\ChannelPool;
Container::getInstance()->bind('db', function ($app) {
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
$config = $app->config->pull('database');
return new DatabasePool($config, new ChannelPool(60));
});
其中60是连接池大小,按max_request * 2估算(如Swoole配置max_request=3000,则设60~100);太大易触发OOM,太小导致频繁创建销毁连接。
【必须显式调用Db::pool()->table(),Db::table()仍走传统单例连接】——这是最容易踩的坑,90%的“复用失败”源于此处未改调用入口。
验证连接是否真正复用
启动Swoole服务后,执行mysql -u root -p -e "show processlist;",观察Id列数字是否稳定在池大小附近(如60),而非随并发请求数线性暴涨。
在业务代码中插入日志:Log::info('conn uid: ' . Swoole\Coroutine::getuid() . ', conn hash: ' . spl_object_hash($connection));,发起两个并发请求,确认两次日志中的conn hash不同,但conn uid各自独立——这表明协程ID隔离生效。
若show processlist中连接数持续攀升,立即检查:是否所有DB操作(含模型save、事务DB::transaction)都已替换为Db::pool()前缀。










