ThinkPHP 本身无数据库连接池且不会导致连接泄露,其采用 PDO/MySQLi 短连接,FPM 模式下脚本结束自动释放;连接数持续上涨多因 Swoole 环境未适配、手动未关闭连接或长生命周期对象持有 PDO 实例所致。

ThinkPHP 本身没有数据库连接池,所谓“连接泄露自动修复”是误解。它用的是 PDO 或 MySQLi 的短连接,默认每次请求结束就释放;如果你看到连接数持续上涨、MySQL 报 Too many connections,问题不在 ThinkPHP 框架层的“泄露”,而大概率出在你自己写的长生命周期对象、手动未关闭的 $db->query()、或协程/Swoole 环境下未适配框架行为。
为什么 ThinkPHP 不会自己泄露 MySQL 连接
ThinkPHP 5.x/6.x 的数据库操作基于 think\db\Connection,所有查询最终走 PDO::query() 或 mysqli_query(),底层由 PHP 运行时在脚本结束时自动清理连接资源(除非显式用了 PDO::ATTR_PERSISTENT)。也就是说:普通 FPM 模式下,根本不存在“连接不释放”的框架级 bug。
- 你看到的“连接堆积”,通常是 Swoole/Workerman 等常驻内存模型里,
Db::connect()被反复调用却没复用连接实例,导致每次新建一个 PDO 对象 - 手写了
new \PDO(...)但没 unset 或没 try/catch 后 close,且放在了全局变量或静态属性里 - 开启了持久连接(
'params' => [PDO::ATTR_PERSISTENT => true]),但没控制好并发或连接复用逻辑,造成连接被占满却不回收
如何强制回收“疑似泄露”的连接(仅限 Swoole 场景)
在 Swoole HTTP Server 或 Task Worker 中,必须主动管理数据库连接生命周期。ThinkPHP 官方不维护连接池,但你可以用 Db::close() 手动切断当前连接,并配合 Db::setConfig() 动态重置连接配置来“模拟回收”。
- 每次请求结束前加
Db::close();(TP6.1+ 支持,TP5 需自行扩展或反射调用Connection::destroy()) - 避免在
__construct或static $instance中提前初始化 Db 实例,改用闭包或 request 生命周期内按需获取 - 若用了 Swoole + 协程 MySQL,别混用
think\db,直接上swoole_mysql或co\mysql,否则协程切换时连接状态不可控
示例(TP6.1+ Swoole HTTP Server 中间件):
立即学习“PHP免费学习笔记(深入)”;
public function handle($request, \Closure $next)
{
$response = $next($request);
Db::close(); // 强制关闭当前请求绑定的连接
return $response;
}
检查是否真有连接泄露:看 MySQL 实时连接和堆栈
别猜,直接查。连上 MySQL 执行:SHOW PROCESSLIST;,重点关注 Command 列为 Sleep 且 Time 超过 30 秒的连接,再结合 Host 和 db 判断来源。如果大量连接来自同一个 PHP-FPM worker IP+端口,说明是代码里没释放;如果是 Swoole 进程 ID 固定、连接数随请求数线性增长,那就是没做连接复用或 close。
- Linux 下用
lsof -p {php_pid} | grep mysql看单个进程打开的 socket 数 - TP 日志里搜
SQLSTATE[HY000] [1040] Too many connections,确认是不是峰值压垮而非泄露 - 禁用所有自定义
Db::connect(),只用Db::table()->select(),观察是否还涨——能压住,说明问题出在你手动接管连接的地方
真正难处理的不是“怎么回收”,而是“哪个对象持有连接却不释放”。Swoole 环境下,static $db、全局缓存、事件监听器里的闭包,都可能悄悄留住 PDO 实例。盯住 debug_zval_count() 或 xdebug_debug_zval() 查引用计数,比改配置有用得多。


















