不是调高max_connections就能解决,90%的“too many connections”是连接未释放、复用失控或配置不一致导致的假性耗尽;需统一配置收口、禁用持久连接、缩短mysql wait_timeout、用redis监控真实连接数。

直接结论:不是调高 max_connections 就能解决,90% 的 “too many connections” 是连接没释放、复用失控或配置不一致导致的假性耗尽。
Db::connect() 多次调用真会新建连接吗
会,但只在连接配置「完全一致」时复用。ThinkPHP 默认开启连接池复用,但只要 hostname、database、username、charset、params 中任一字段不同,就会创建新连接且永不归还。
- 常见踩坑点:
utf8和utf8mb4混用;日志中间件里悄悄传了['debug' => false];不同模块硬编码了不同 host(127.0.0.1vslocalhost) - 验证方法:在出问题时调用
Db::getLinks(),看返回数组 key 是否异常重复 - 统一收口:所有
Db::connect($config)必须走一个配置工厂函数,禁止散落在各处
禁用持久连接(pconnect)是第一步
ThinkPHP 默认不启用持久连接,但如果你在数据库配置里手动加了 PDO::ATTR_PERSISTENT => true,或用了 'pconnect' => true,高并发下极易堆积“僵尸连接”——MySQL 已断开,PHP 还以为连着。
- 必须显式关闭:
'options' => [PDO::ATTR_PERSISTENT => false] - 同时配
PDO::ATTR_TIMEOUT => 5,让失败连接快速报错,不卡住进程 - TP5 的
db()助手函数第三个参数$force默认是false,但如果误写成true,每次调用都强制重连,等于主动制造连接风暴
MySQL wait_timeout 不是调大,而是必须调小
默认 28800 秒(8 小时)会让空闲连接长期占位。ThinkPHP 不主动探测连接有效性,等下次查询时才报 MySQL server has gone away,此时框架尝试重连,若并发高,就形成“重连风暴 → 连接数暴涨 → 真满”的死循环。
- MySQL 配置中设:
wait_timeout = 60,interactive_timeout = 60 - 配合应用层心跳:每 240 秒执行一次
SELECT 1(可在中间件或定时任务中做) - 监控关键指标:
SHOW STATUS LIKE 'Threads_connected',持续高于max_connections * 0.8就要干预
连接数监控不能靠 Db::getConnect()
Db::getConnect() 返回的是当前请求持有的连接对象,不是连接池水位。真正有效的监控必须独立于框架逻辑,用外部存储计数。
- 在每次
Db::connect()前执行:Redis::incr('db:active_connections') - 在事务结束或对象销毁时(务必用
__destruct或finally)执行:Redis::decr('db:active_connections') - 轮询
Redis::get('db:active_connections'),超阈值立即告警(钉钉/邮件),而不是等用户报错 - 别信
PDO::getAttribute(PDO::ATTR_CONNECTION_STATUS),它只告诉你连接“是否还活着”,不告诉你“池子里还有几个坑”
最常被忽略的一点:PHP-FPM 的 pm.max_requests 设得太小,会导致频繁重启 worker,反而加剧连接重建;设得太大,又可能累积未释放资源。建议从 500 起调,结合 Threads_connected 波动观察。连接问题从来不是单点配置能搞定的,它是数据库、框架、运行环境三者交界处的毛细血管级问题。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











