ThinkPHP 8 无内置连接池,“连接池泄漏”实为连接复用机制破坏导致的假性耗尽;主因是事务未提交、对象生命周期失控或 Swoole/Workerman 下连接未显式关闭,表现为 MySQL Threads_connected 持续上涨、频繁出现“gone away”或“too many connections”错误。

ThinkPHP 8 没有内置连接池,所谓“连接池泄漏”其实是连接复用机制被破坏后,连接长期未释放导致的假性耗尽——本质是事务卡住、对象生命周期失控或 Swoole/Workerman 环境下连接未显式关闭。
为什么 TP8 的“连接池”会漏连接?
TP8 的数据库连接管理依赖 think\db\Connection 的缓存复用逻辑,不是真正的连接池。它只在同一个请求周期内复用已创建的连接实例;一旦连接被借出后没走完释放路径(比如事务没提交、Db 被赋给 static 属性、协程中跨生命周期复用),连接就会滞留在 PDO 层,表现为:
- MySQL 的
SHOW STATUS LIKE 'Threads_connected'持续上涨,且不随请求结束回落 - 日志里频繁出现
MySQL server has gone away或Too many connections - 在 Swoole 或 Workerman 场景下,内存 RSS 不断增长,
php-meminfo显示大量PDO和PDOStatement实例残留
如何定位具体哪段代码卡住了连接?
别猜,直接看数据库端的 sleep 连接和 PHP 端的借用堆栈。重点查三类现场:
- 所有手动调用
startTrans()的地方,确认是否 100% 配对了commit()或rollback()——尤其注意异常分支、return 提前退出、中间件里开启但没在后续处理完 - 检查有没有把
Db::name('user')或new UserModel()赋值给static、global、或作为闭包use变量长期持有 - Swoole/Workerman 中,是否在
onMessage或定时器回调里直接复用了 Request 生命周期外的Db实例(TP8 默认不协程安全)
快速验证:临时在 app/common/monitor/TrackedConnection.php 里把告警阈值设为 3 秒,跑压测,看日志里哪个 trace 最常出现。
立即学习“PHP免费学习笔记(深入)”;
TP8 下最有效的修复动作
不是升级框架版本就能解决,必须改代码+调配置。以下动作缺一不可:
- 数据库配置中强制启用部署模式:
'deploy' => 1(必须是整数,不能写true或'1'),并设'pool_size' => 20控制空闲连接上限 - 补全 PDO 参数:
PDO::ATTR_TIMEOUT => 5、PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"、'break_reconnect' => true——三项必须共存,否则重连时可能卡死 75 秒 - 在 Swoole/Workerman 场景下,禁用持久连接:
'params' => [PDO::ATTR_PERSISTENT => false],并在每次数据库操作后显式置空:$db = null; - 所有命令行任务(如队列消费)必须包裹
try...finally,确保Db::close()或事务兜底执行
Docker 环境下的特殊陷阱
Mac M1 + Docker 组合最容易误判:MySQL 容器默认监听 socket,而 TP8 的 hostname => 'localhost' 会让 PDO 尝试走本地 socket 文件(失败),转而 fallback 到 TCP 的 3306,但此时 host 解析错乱,导致连接反复新建又断开。必须:
- 把
hostname改成数据库容器名(如'mysql'),绝不能写'127.0.0.1'或'localhost' - 确认
hostport是整数3306,不是字符串'3306'(后者会让 PDO 忽略该端口) - 删掉
runtime/cache/和runtime/log/,避免旧缓存干扰新配置生效
真正难缠的不是连接没还,而是你根本不知道它被谁借走了、在哪卡住了——所以监控要前置,清理要闭环,环境配置要咬死细节。



















