可通过监听 Connect 事件并记录堆栈与时间戳,结合 PDO 非持久连接配置和 request_id 日志,精准追踪每次请求的数据库连接数及复用情况。

怎么查出 ThinkPHP 每次请求用了几个数据库连接
ThinkPHP 默认用 PDO,但连接是否复用、何时新建、有没有泄露,光看配置文件看不出。得从运行时抓真实行为。
最直接的办法是打开数据库日志 + 在 think\db\Connection 类里埋点(不改框架源码,用事件监听更安全):
- 在
app\common\Event.php或全局中间件中监听Connect事件,记录每次connect()调用的堆栈和时间戳 - 配合开启 PDO 的
PDO::ATTR_EMULATE_PREPARES => false和PDO::ATTR_PERSISTENT => false(默认就是 false),确认不是持久连接干扰判断 - 注意:TP6 的
Db::connect()如果传了不同配置名(比如'mysql2'),会新建独立连接池,不算复用
为什么 config/database.php 里写了 'deploy' => 1 还是连了两次
部署模式(deploy)只影响读写分离的路由逻辑,跟连接对象是否复用无关。它控制的是「用哪个连接实例去执行 SQL」,不是「要不要 new 一个新连接」。
常见误判场景:
- 模型里写了
protected $connection = 'mysql2',但全局没配这个连接名 → TP 自动 fallback 到默认连接,并新建一个,导致重复连接 - 手动调用
Db::connect('mysql')多次,且参数不同(比如 host 或 database 不同),TP 认为是不同连接配置,各自缓存一份 - 命令行任务(如
php think test)没走 HTTP 生命周期,连接不会被自动释放,容易累积
连接复用失效的三个典型代码写法
不是配置不对,而是代码触发了连接隔离。以下写法会让 TP 认为“这是另一个连接需求”,从而放弃复用:
- 在事务块外多次调用
Db::connect(['hostname' => '127.0.0.1']),哪怕 hostname 一样,只要数组键顺序或值类型不同(如'port' => '3306'字符串 vs'port' => 3306整型),缓存 key 就不同 - 使用
Db::name('user')->connection('mysql')->select(),显式指定 connection 名,但该名未在配置中声明 → TP 新建临时连接,不进连接池 - 在协程环境(如 Swoole)下没启用
think-swoole扩展,TP 的静态连接池被多协程共享,导致连接被意外关闭或复用错乱
用 show processlist 看实际连接数比预期多怎么办
PHP-FPM 模式下,每个 worker 进程有自己的连接池,show processlist 显示的是 MySQL 侧全部活跃连接,不是单次请求用量。要定位问题,得绑定到具体请求:
- 在入口文件
public/index.php开头加define('APP_DEBUG_CONN', true),然后在think\db\Connection::__construct中加日志,打印$this->config['hostname'].':'.$this->config['hostport']和当前 request_id - 用
SHOW PROCESSLIST WHERE Command != 'Sleep'配合应用日志里的 request_id,反查哪次请求留下的连接没关 - 重点检查
try...catch块里有没有漏掉$db->close()(虽然 TP 一般自动关,但手动获取连接后又没用完就 return,容易残留)
连接复用不是黑盒魔法,它依赖配置一致性、生命周期对齐、以及不绕过框架连接管理。最容易被忽略的是:你以为在复用,其实只是 TP 把上一次的连接对象又塞给了你,而那个连接早就在上次请求结束时被 close() 了 —— 真正复用的前提,是连接还活着。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










