连接池耗尽需通过Redis埋点监控活跃连接数并设阈值告警;PDO超时与MySQL wait_timeout不匹配会导致“僵尸连接”假性耗尽;Db::connect()多次调用会因配置差异新建连接;告警须含时间、连接数、慢SQL三要素且限频。

连接池耗尽时怎么立刻知道?
ThinkPHP 本身不提供数据库连接池监控能力,think-orm 和 Db 类只管理单次连接生命周期,没有内置连接数统计、空闲/活跃连接监控或阈值告警。所谓“连接池监控”,实际得靠外部手段补全。
最直接有效的方式:在连接建立前埋点,用 Redis 记录当前活跃连接数,并设置阈值触发通知。不要依赖 Db::getConnect() 或 PDO::getAttribute(PDO::ATTR_CONNECTION_STATUS) —— 它们返回的是连接状态,不是连接池水位。
- 每次调用
Db::table()或Db::connect()前,执行Redis::incr('db:active_connections') - 在
__destruct或事务结束回调中执行Redis::decr('db:active_connections')(需确保无论成功失败都执行) - 用
Redis::get('db:active_connections')定期轮询,超过max_connections * 0.8就发钉钉/邮件
为什么 pdo_mysql 的 wait_timeout 会导致假性耗尽?
MySQL 默认 wait_timeout=28800(8 小时),但 ThinkPHP 的连接复用逻辑不会主动探测连接是否被服务端断开。当连接空闲超时后,下次 Db::select() 可能报 MySQL server has gone away,框架会尝试重连,但若并发高,重连失败堆积,就会误判为“连接池满”。
这不是连接池真满了,而是大量“僵尸连接”占着连接句柄却无法复用。
立即学习“PHP免费学习笔记(深入)”;
- 在数据库配置里显式加
'options' => [PDO::ATTR_TIMEOUT => 5],让 PDO 快速失败而非卡住 - 把 MySQL 的
wait_timeout改小(比如 300),配合应用层心跳(如每 240 秒执行一次SELECT 1) - 禁用
DB_DEBUG = true环境下的长连接缓存,避免调试时连接泄漏难发现
Db::connect() 多次调用会不会真的新建连接?
会,但有条件。ThinkPHP 默认开启连接池复用,前提是连接配置完全一致(包括 hostname、database、username、charset、params 全字段相等)。只要有一个键值不同,就会创建新连接,且不会自动归还。
常见踩坑点:日志中间件里偷偷改了 Db::connect(['debug' => false]),或不同模块用了 utf8 和 utf8mb4 两种 charset,结果产生多套隔离连接池,总量翻倍却不自知。
- 检查所有
Db::connect($config)调用,统一收口到一个配置工厂函数 - 用
Db::getLinks()打印当前所有连接标识,确认是否出现意外的重复 key - 避免在循环里写
Db::connect(['port' => $i])—— 每个 port 都算独立连接池
告警该发给谁?怎么避免刷屏?
连接数突增往往伴随慢查询或死锁,不是孤立事件。单纯发“连接数 > 90%”意义不大,必须绑定上下文才有可操作性。
真正有用的告警要带三要素:时间戳、当前活跃连接数、最近一条慢查询 SQL(从 think-sql 日志里捞)。
- 用
Db::getQueryLog()拿最近 5 条 SQL,过滤出执行时间 > 1000ms 的那条,作为告警正文 - 告警频率限制:10 分钟内同 IP + 同 DB 只发一次,用
Redis::setex('alert:db:192.168.1.100:mysql', 600, 1)控制 - 别只发给运维;把告警 Webhook 接入研发群,附上
tail -n 20 runtime/log/sql/*.log最近片段链接
连接池监控真正的难点不在采集,而在区分“真饱和”和“假阻塞”。很多团队花力气搭 Prometheus + Grafana,结果发现 80% 的告警都是因为某条没加索引的 LIKE '%关键词%' 把连接拖死了——监控只是镜子,照出问题,但得靠人去看 SQL。


















