答案是:先确认threads_connected是否≥max_connections×0.9且max_used_connections持续上涨,再查非sleep和长sleep连接定位卡顿与泄漏源,最后检查php持久连接、fpm子进程数、代码漏关、连接池超时对齐及dns/健康检查干扰。

PHP 8.3 网站访问突然变慢、接口超时、页面报错“SQLSTATE[HY000] [1040] Too many connections”,说明 MySQL 的连接槽位已被占满,新请求无法获取数据库连接——这不是流量突增的错觉,而是连接生命周期失控的明确信号。
第一步:确认是不是真满了
用 phpMyAdmin 或命令行连上数据库,执行三句命令:
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
若 Threads_connected ≥ max_connections × 0.9 且 Max_used_connections 持续上涨不回落,说明连接在堆积,不是瞬时抖动。注意:Threads_connected 不包含已泄露但未关闭的连接(比如脚本异常退出前没执行 $pdo = null),压测中实际值可能更高。
第二步:查谁在占着连接不放
执行 SELECT * FROM information_schema.PROCESSLIST WHERE Command != 'Sleep' ORDER BY Time DESC LIMIT 10;
重点看 State 列为 Sending data、Sorting result、Locked 的连接,它们正在执行慢 SQL 或卡在锁等待;再执行 SELECT * FROM information_schema.PROCESSLIST WHERE Command = 'Sleep' AND Time > 60 ORDER BY Time DESC LIMIT 10;
大量 Sleep 时间超过 60 秒的连接,极大概率是 PHP-FPM 工作进程挂起后未释放的“僵尸连接”,尤其集中在同一 Host IP 下——这说明旧实例未优雅下线,或持久连接复用错位。
第三步:定位 PHP 层泄漏源头
方法一:检查是否误开持久连接
在 PDO DSN 中出现 ;persist=true,或 mysqli 构造时用了 p:localhost 前缀,且未配 mysqlnd 自动重连,就会导致连接长期滞留。PHP 8.3 默认禁用 mysqli.reconnect,断连后直接抛异常,不会 fallback,反而触发重试建连风暴。
方法二:确认 FPM 子进程数是否越界
查 pm.max_children 值,它必须 ≤ MySQL 的 max_connections 并预留 20% 余量。例如 MySQL 设了 500,FPM 就不能超过 400;若用了 Swoole 协程服务,pm.max_children 必须设为 1,否则 FPM 和协程双层连接池会叠加占用。
方法三:盯死代码里有没有漏关连接
在循环中写 new PDO(...) 查库、没用 try/finally 包裹 $pdo = null 或 $pdo->close()、ThinkPHP 8 在 FPM 下误配 'pool' => true(该配置仅在 Swoole 协程生效,FPM 下每次 Db::connect() 都新建 PDO,异常时连接卡 Sleep)——这些行为在 PHP 8.3 的严格错误模式下更容易暴露为连接堆积。
第四步:验证连接池与数据库超时是否对齐
第一步:查 HikariCP(如 Laravel 使用)或 PDO 层空闲参数
检查 idle-timeout 是否小于 MySQL 的 wait_timeout 至少 30 秒。若 MySQL wait_timeout=28800(8 小时),而应用侧 idle-timeout=600000(10 分钟),那连接池里的空闲连接会在数据库端被单方面 kill,取用时报 Communications link failure,触发重建逻辑,连接数指数增长。
第二步:确认是否启用连接有效性检测
未配置 connection-test-query=SELECT 1 且 test-on-borrow=true 时,失效连接会被复用,失败后才重建,形成连接风暴。PHP 8.3 的 PDO 默认不开启此检测,必须显式设置。
第三步:检查 DNS 缓存与健康检查干扰
K8s preStop 未设 sleep 30、负载均衡健康检查间隔短于 wait_timeout、DNS 缓存未刷新,都会导致流量切换时旧连接未释放、新连接又涌入。此时 SHOW PROCESSLIST 会出现同一 Host 下 ID 集中暴增的现象。
第五步:快速应急操作
① 立即执行:SET GLOBAL wait_timeout = 300; 和 SET GLOBAL interactive_timeout = 300;,让空闲连接 5 分钟自动断开,缓解堆积压力。
② 若主端口完全不可用,确认 MySQL 是否启用了 admin_port(如 33062):执行 grep -E 'admin_address|admin_port' /etc/my.cnf,若存在且配置正确,可用 mysql -h127.0.0.1 -P33062 -uroot -p 登录执行紧急 kill。
③ 手动清理长 Sleep 连接:KILL [Id];(从 PROCESSLIST 中取 Id),优先杀 Time > 300 的连接,避免误杀正在执行的事务。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











