Db::connect()在协程中总新建连接,因其未Hook PDO、无连接池,导致连接数暴涨;应改用Swoole协程MySQL或连接池,并确保池为Worker级单例,fetch需紧跟query,模型层禁用直接new连接。

协程环境下 Db::connect() 为什么总是新建连接?
ThinkPHP 默认的 Db::connect() 在协程中每次调用都会创建新连接,不是复用——因为它底层没走 Swoole 的协程 Hook,而是直接调用了 PHP 原生 PDO 构造器。协程切换时,原连接句柄无法跨协程传递,又没做连接池管理,结果就是连接数暴涨、MySQL 报 Too many connections。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 禁用 ThinkPHP 自带的连接管理,改用 Swoole 提供的
Co\MySQL或更推荐的swoole_mysql_pool(需配合ext-swoole≥ 4.8.0) - 在
config/database.php中把'type' => 'mysql'改为自定义驱动,或直接绕过 Db 类,在业务逻辑里用Swoole\Coroutine\MySQL - 务必关闭 PDO 的
PDO::ATTR_PERSISTENT,它和协程不兼容,会引发连接错乱
怎么让 Swoole 连接池真正被 ThinkPHP 请求复用?
很多人配了 Swoole\Coroutine\Pool,但每个 HTTP 请求仍各自 new 一个池实例,根本没共享——池对象必须是单例,且生命周期绑定到 Worker 进程,不能放在每次请求初始化的代码里。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在
server.php或 Swoole 启动脚本中,用静态属性或Swoole\Runtime::setHookFlags()初始化一次池,例如:MySqlPool::getInstance() - ThinkPHP 的
app\common\middleware\CoroutinePoolMiddleware可在请求开始前从池取连接,__destruct或defer归还,避免忘记释放 - 注意池大小配置:设太小会排队阻塞,太大可能触发 MySQL max_connections;建议按
worker_num × 2 ~ 4预估
Swoole\Coroutine\MySQL 查询后 fetch() 返回 null 怎么办?
这不是 Bug,是协程 MySQL 的行为差异:执行 query() 后必须立刻 fetch() 或 fetch_all(),否则下次协程切换再取就失效了——因为结果集只在当前协程栈内有效,不支持跨协程延迟读取。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 禁止在
query()和fetch()之间插入co::sleep()、go()或其他 I/O 操作 - 如果要组合多个查询结果,先
fetch_all()存到变量里,再做后续处理 - 调试时可加
var_dump($mysql->errno, $mysql->error),常见错误如2013: Lost connection to MySQL server during query往往就是 fetch 不及时导致
协程数据库操作在模型层怎么安全封装?
直接在 app\model\User.php 里写 new Swoole\Coroutine\MySQL() 是错的:模型类会被多次实例化,池管理、连接归属、事务边界全乱套。协程资源必须和生命周期对齐,不能藏在无状态的 Model 里。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 把数据库操作抽成独立服务类(如
app\service\UserService.php),构造函数接收连接对象或池实例,确保连接来源清晰 - 事务必须用
$mysql->begin()/commit()/rollback()手动控制,ThinkPHP 的Db::transaction()不支持协程 MySQL - 日志和异常捕获要覆盖
$mysql->connect()失败场景,Swoole 协程 MySQL 连接失败不抛异常,只设$mysql->connected = false
协程不是加个 go() 就高并发了,连接池的“池”字背后是连接归属、超时控制、异常归还三件事,漏掉任何一环,压测时就会在凌晨三点收到报警。


















