ThinkPHP 无内置连接池,连接泄露主因是事务未提交/回滚或对象生命周期异常延长;可通过数据库 sleep 连接、自定义 TrackedConnection 监控借用时长、Swoole 下强制 close 和禁用持久连接来发现与防范。

ThinkPHP 连接池里连接没还回去,怎么发现?
ThinkPHP 本身不内置连接池,所谓“连接池”其实是 PDO 连接复用 + 连接管理(比如 think\db\Connection 的连接缓存机制),真正的连接泄露发生在:请求结束时,Connection 实例未被销毁、或事务未提交/回滚导致连接被长期占用。
最直接的告警信号是数据库端出现大量 sleep 状态连接,且持续时间远超 PHP 脚本生命周期(比如 >30s)。这时不是 TP “漏还”,而是你代码里有地方没走完释放流程。
- 检查是否在中间件、命令行任务、异步回调中手动调用了
startTrans()却没配对commit()或rollback() - 确认有没有把
Db实例赋值给全局静态变量或static属性,导致连接对象生命周期被意外延长 - 避免在闭包或协程(如 Swoole)中直接复用 Request 生命周期外的
Db对象 —— ThinkPHP 6.1+ 默认不支持协程安全连接复用
怎么让 TP 主动检测连接占用超时?
TP 没提供“连接借用超时自动告警”机制,但你可以通过钩子 + 自定义连接类实现轻量监控。核心思路是在连接被取出时打上时间戳,在连接归还前校验耗时。
示例做法(适用于 TP 6.x):
立即学习“PHP免费学习笔记(深入)”;
// app/common/monitor/TrackedConnection.php
class TrackedConnection extends \think\db\Connection
{
protected $borrowTime = 0;
<pre class="brush:php;toolbar:false;">public function getConnection($config = [])
{
$conn = parent::getConnection($config);
$this->borrowTime = microtime(true);
return $conn;
}
protected function close()
{
if ($this->borrowTime > 0 && (microtime(true) - $this->borrowTime) > 10.0) {
\think\facade\Log::warning('db connection borrowed too long', [
'duration' => round(microtime(true) - $this->borrowTime, 2),
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
]);
}
$this->borrowTime = 0;
parent::close();
}}
- 需在数据库配置中指定
'type' => \app\common\monitor\TrackedConnection::class - 10 秒阈值可根据业务调整,注意 CLI 场景(如队列任务)可能天然较长,建议区分环境
- 这个钩子只对单次请求内有效;Swoole 长连接模式下,
close()不会被调用,必须配合onWorkerStart清理逻辑
为什么用了事务就更容易漏还连接?
ThinkPHP 的事务是基于连接绑定的:一旦调用 startTrans(),当前连接就被标记为“事务中”,后续所有同连接的查询都会复用它。如果事务中途抛异常且未被捕获,或者 rollback() 被跳过,连接会一直卡在事务状态,PDO 层面无法自动释放。
- 务必在
try...catch中使用事务,且catch块必须调用rollback(),不能只写return - 避免在事务块里调用其他也开启事务的方法(比如另一个 service 的
doSomethingWithTrans()),嵌套事务在 TP 中行为不可靠 - 不要依赖析构函数(
__destruct)做回滚 —— 请求结束时对象销毁顺序不确定,Db实例可能比事务管理器先销毁
连接泄露在 Swoole 下表现更隐蔽,怎么应对?
Swoole Worker 进程常驻,TP 默认的连接复用机制会让同一个连接被反复使用,而 close() 在请求结束时不触发,导致“连接越积越多”假象 —— 实际是连接被复用,但某些请求占着不放。
关键动作只有两个:
- 强制每次请求结束时关闭连接:在
onRequest回调末尾手动调用\think\facade\Db::close() - 禁用连接长复用:数据库配置加
'deploy' => 0, 'persistent' => false,防止 PDO 连接被进程级缓存
注意:这样做会略微增加连接建立开销,但比内存泄漏和连接数打满要可控得多。线上若用 Swoole,别信“TP 自带连接池”这种说法 —— 它没有。
真正难排查的是跨协程上下文丢失导致的连接错绑,这时候连日志都看不出谁借的谁还的。建议核心事务操作统一收口到一个 DB 工厂类里,借还动作显式记录 trace_id 和协程 ID。



















