ThinkPHP 默认无连接池,Db::close() 在 FPM 下仅断开当前 PDO 实例,不释放底层 MySQL 连接;'deploy'=>1 不启用连接池;CLI 需显式关闭;协程环境必须用 Swoole MySQL 驱动并配置 pool_size。

连接池没开,close() 调用无效
ThinkPHP 默认不启用连接池,Db::close() 或手动 $connection->close() 在大多数场景下只是断开 PHP 当前请求的 PDO 实例,但底层 MySQL 连接可能仍被复用(尤其在 FPM 模式下)。这不是 bug,是 PDO 的默认行为:连接由 PHP-FPM worker 生命周期管理,不是按需释放。
实操建议:
- 确认是否真需要“空闲回收”——多数 Web 场景下,连接复用比频繁开关更高效;
- 若确有长连接泄漏(如 CLI 脚本、协程环境),优先检查是否漏写了
try...finally中的Db::close(); - 不要依赖
__destruct自动关闭,ThinkPHP 的连接对象生命周期与请求绑定,析构时机不可控; - FPM 下真正释放连接,靠的是 worker 进程重启或超时回收,不是代码调用
close()。
database.php 里 'deploy' => 1 不等于连接池
很多人误以为开启读写分离('deploy' => 1)就自动带连接池功能。其实它只控制路由逻辑,底层每个节点仍是独立 PDO 实例,无共享连接队列、无空闲连接超时淘汰机制。
实操建议:
- ThinkPHP 6.x 原生不支持连接池,所谓“连接池”需自行集成 Swoole 协程 MySQL 客户端或使用第三方扩展(如
topthink/think-swoole); - 若用 Swoole,必须切换到协程模式,并将数据库配置中的
'type'改为'mysql'(非'pdo_mysql'),否则仍走同步阻塞连接; -
'break_reconnect' => true和'params'里的PDO::ATTR_PERSISTENT => true是两回事:前者是断线重连策略,后者是持久连接(PHP 内部复用,但不跨 worker,且易引发事务残留)。
CLI 环境下连接不释放的典型表现
命令行脚本执行完后,MySQL 的 SHOW PROCESSLIST 里仍看到大量 Sleep 状态连接,状态持续几十秒甚至几分钟——这是 PDO 持久连接(PDO::ATTR_PERSISTENT)未正确关闭,或脚本异常退出导致 __destruct 未触发。
实操建议:
- CLI 脚本务必显式调用
Db::close(),并在register_shutdown_function中兜底; - 禁用持久连接:
'params' => [PDO::ATTR_PERSISTENT => false](默认就是 false,但有人会手动设成 true 后忘记改回来); - 避免在循环中反复调用
Db::connect()创建新实例,应复用单例或注入连接对象; - 注意
Db::transaction()未提交/回滚时,连接会被占用,直到脚本结束或 GC 触发,容易卡住。
协程环境下连接复用必须配 swoole.mysql
在 ThinkPHP + Swoole 协程项目中,若仍用默认 pdo_mysql 驱动,每个协程都会新建一个同步连接,不仅无法复用,还会因阻塞导致协程调度失效,连接数暴增。
实操建议:
- 必须安装
swoole扩展并启用mysql协程客户端支持; - 数据库配置中设置
'type' => 'mysql',并移除'dsn',改用'hostname'、'username'等字段; - 协程连接池大小由
'pool_size'控制(如'pool_size' => 20),空闲连接超时由'pool_timeout'控制(单位秒),这两个参数在原生 PDO 配置里完全无效; - 协程模式下
Db::close()变为归还连接到池,不是销毁,所以调用后还能继续用同一Db实例。
连接空闲回收不是开关一开就生效的事,它高度依赖运行模型(FPM / CLI / Swoole)、驱动类型(PDO / Swoole MySQL)、以及你是否真的在管理连接生命周期。最容易被忽略的,是把“连接关闭”等同于“资源释放”,而没意识到连接背后还有进程、协程、TCP 层的上下文。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











