“Too many connections”是MySQL拒绝新连接的信号,源于应用层连接泄漏或误配,需查Threads_connected与max_connections差值,重点排查CLI/Swoole手动管理缺失、PDO超时未设、无效pool配置及deploy=1未显式启用。

“Too many connections”不是 ThinkPHP 的锅,是 MySQL 明确拒绝新连接的信号——它说明 Threads_connected 已逼近或达到 max_connections 上限。别急着改数据库配置,90% 的情况是应用层在疯狂建连却不释放,或者误配了根本不存在的“连接池”参数。
查清到底是连接泄漏还是真扛不住并发
先登录 MySQL 执行两条命令:
-
SHOW STATUS LIKE 'Threads_connected';—— 看当前真实连接数 -
SHOW VARIABLES LIKE 'max_connections';—— 看上限值
如果前者长期稳定在 140+/151(默认值),且 SHOW PROCESSLIST 里大量状态为 Sleep、Time 超过 30 秒,基本就是连接没释放;如果只是压测时瞬间冲到上限,再回落,则更可能是并发突增+连接复用率低。
重点盯 CLI 任务和 Swoole 任务:FPM 每次请求结束会自动关连接,但 CLI 脚本不调 Db::close() 就永远挂着;Swoole 下连接常驻内存,一个没关的 Db::connect() 会在整个 worker 生命周期内持续占用。
立即学习“PHP免费学习笔记(深入)”;
database.php 里必须配对生效的两个关键参数
ThinkPHP 的 timeout 字段是无效的,真正起作用的是 params 数组里的 PDO::ATTR_TIMEOUT;而 break_reconnect 单独开启也没用,它只在连接失效后决定是否重试,但重连本身可能卡死。
- 必须同时设置:
'break_reconnect' => true+'params' => [\PDO::ATTR_TIMEOUT => 5] - 漏掉
PDO::ATTR_TIMEOUT,PDO 在建立新连接时会卡在系统级 TCP 超时(Linux 默认 75 秒),导致请求假死 - 云数据库(如阿里云 RDS)
wait_timeout常设为 300 秒,此时不配break_reconnect就会反复报MySQL server has gone away
别碰 pool 配置,TP 原生不支持连接池
ThinkPHP 官方没有数据库连接池实现。database.php 中写的 'pool' => ['max_active' => 20] 或 'min_idle' => 5 这类配置,在 FPM 下完全被忽略;即使用了 think-swoole,它的 pool 也只是协程内连接缓存,不是传统连接池。
- FPM 模式下强行加
pool配置,既不生效,还容易误导排查方向 - Swoole 下若设
'max_active' => 1000,每个 worker 会尝试维持 1000 个空闲连接,4 个 worker 就直接打满 MySQL 的max_connections - 真正有效的复用开关是
'deploy' => 1,但它只对同 host/port/database/user 的请求生效,且必须显式配置,不能靠 fallback
CLI 和 Swoole 场景下连接必须手动管理
FPM 的连接生命周期由 PHP 自动控制,但 CLI 和 Swoole 不会自动释放连接对象,这是连接数失控最隐蔽的来源。
- 所有 CLI 命令(如队列消费、定时任务)末尾必须加
Db::close(),不能依赖脚本退出 - Swoole 任务中避免在
__construct或同步中间件里提前调Db::connect(),应在协程内用go(function () { ... })包裹 - 事务块必须用
try-catch包裹,catch里强制$db->rollback(),否则连接被永久独占 - 多库操作(如
Db::connect('log_db'))会新建独立连接,禁止在循环里反复调用
最易被忽略的一点:Swoole 下 deploy => 1 开启后,空闲连接不会自动清理,pool_size 只限制数量,不触发回收——你得自己控制连接生命周期,而不是指望框架“智能归还”。



















