TP6的Db连接不会自动释放,因其默认复用连接且常驻进程(如queue:work)中__destruct不触发,导致MySQL端堆积Sleep连接;需在业务结束时手动调用getConnection()->close()释放。

TP6 没有公开的 close() 方法,但连接资源必须手动释放——尤其在长生命周期进程(如 queue:work)中,否则会堆积连接导致 too many connections 错误。
为什么 TP6 的 Db 连接不会自动释放?
TP6 的 Db 类默认使用连接池机制,每次调用 Db::table() 或 Db::name() 都会复用已建立的连接(PDOConnection 实例),而不会在查询结束后断开。PHP 脚本结束时虽会触发析构函数调用 close(),但在 queue:work 这类常驻进程中,单个进程持续运行数小时甚至数天,__destruct() 根本不会执行,连接就一直挂在 MySQL 服务端。
常见错误现象:SQLSTATE[HY000] [1040] Too many connections,MySQL 的 show processlist 中能看到大量 Sleep 状态的旧连接。
- 不是「没连上」,而是「连太多没关」
- 不是配置问题,是连接生命周期管理缺失
- 不是并发高才出问题,单个 worker 持续消费几十条消息就可能突破连接上限
如何安全调用 close()?
TP6 底层 PDOConnection 类确实实现了 close() 方法,但它被封装在 Connection 抽象类中,且未暴露为公共接口。不能直接写 Db::close(),但可以通过反射或强制访问私有方法绕过限制:
立即学习“PHP免费学习笔记(深入)”;
推荐方式(兼容性好、不改 vendor):
$connection = Db::getConnection(); $connection->close();
说明:
-
Db::getConnection()返回当前默认连接实例(think\db\Connection子类) - 该实例继承自抽象类
think\db\Connection,其close()方法是 public 的(尽管文档未列出) - 调用后
$connection->linkID、$connection->linkRead等句柄被置为null,下次查询会重建新连接
⚠️ 注意:不要用 Db::__destruct() —— 这是非法调用,PHP 会报 Fatal error: Call to private method;也不建议用反射强行调用,升级后易断裂。
多库场景下如何指定关闭某个连接?
当项目使用多个数据库配置(如 'connections' => ['mysql1', 'mysql2']),且只希望关闭其中某一个时,不能依赖 Db::getConnection()(它只返回 default):
正确做法是显式获取并关闭指定连接:
$conn1 = Db::connect('mysql1');
$conn1->close();
$conn2 = Db::connect('mysql2');
$conn2->close();
关键点:
-
Db::connect('xxx')是工厂方法,每次调用都返回独立的连接实例(非单例) - 即使配置了
'params' => [\PDO::ATTR_PERSISTENT => true],close()仍有效——它清空的是 PHP 层的 PDO 句柄引用,不干预底层 socket - 如果后续还需用该连接,必须重新
Db::connect('xxx'),不能复用已close()的对象
放在哪里调用最稳妥?
必须确保在「本次业务逻辑彻底结束之后、进程继续循环之前」执行,否则可能影响后续查询。对于队列任务,最佳位置是闭包末尾或 finally 块:
Bus::dispatch(new ProcessJob($data))->onQueue('default')->delay(0);
// 在 job handle() 方法内
public function handle()
{
try {
Db::table('orders')->where('id', $this->id)->update(['status' => 1]);
} finally {
Db::getConnection()->close(); // ✅ 放在这里,无论成功失败都释放
}
}
容易踩的坑:
- 在事务中调用
close()后再commit()→ 报错PDOException: SQLSTATE[HY000]: General error: 2013 Lost connection - 在模型回调(如
saving)里提前close()→ 后续模型操作直接失败 - 用完
Db::name()就立刻close(),但同一请求中还有其他Db查询 → 导致二次连接开销上升
真正要关的,是那些「确定不会再用、且生命周期远超单次 HTTP 请求」的连接——比如队列 worker、定时脚本、CLI 命令。HTTP 请求场景下,让 PHP 自动回收更省心。



















