在phpEnv这类本地集成开发环境中,很多项目做到一半突然页面打不开,查日志发现MySQL抛出了too many connections的错误。这通常是PHP侧频繁创建短连接且未合理复用,导致数据库端的连接数被迅速占满。理解连接的产生与回收机制,再用好连接池或持久化方案,才能从根源上解决溢出问题。

一、数据库连接数溢出的底层原因
当PHP通过mysqli或PDO扩展请求MySQL时,默认行为是每次脚本执行新建一条TCP连接,脚本结束后再断开。在phpEnv的Apache或Nginx+PHP-FPM模式下,若并发请求多,同一秒可能涌进几十个CGI进程,每个都开新连接,而MySQL的max_connections往往只设了151。连接来不及释放,新请求就被拒绝。
另一个容易被忽略的点是,即使脚本退出,MySQL服务端并不会立刻销毁连接,而是要等wait_timeout或interactive_timeout秒数到期。如果PHP端没显式close,且服务端超时设得很大,连接就会处于Sleep状态堆积。下面这段调试代码可以查看当前连接分布:
<?php
// 查看MySQL当前连接状态
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root');
$stmt = $pdo->query("SHOW PROCESSLIST");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo $row['Id'] . ' ' . $row['User'] . ' ' . $row['Command'] . ' ' . $row['Time'] . "n";
}
二、phpEnv中的三种连接管控方案
1. PDO持久化连接
PDO自带持久化属性ATTR_PERSISTENT,开启后PHP-FPM进程会缓存连接,后续请求直接复用,不再频繁握手。在phpEnv里改代码即可,不用动环境配置。但要注意持久连接属于进程级,若PHP-FPM有10个子进程,最多也会占10条连接,不会无限涨。
示例代码如下,在构造函数传入持久化参数:
<?php
$opts = [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
try {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root', $opts);
} catch (PDOException $e) {
echo '连接失败: ' . $e->getMessage();
}
这种方案的优点是改动小、兼容老项目;缺点是无法跨进程调度,且在CLI模式下意义不大。如果phpEnv里跑的是传统FPM应用,此方式最省心。
2. Swoole协程连接池
若你在phpEnv中启用了Swoole扩展,可以用协程MySQL或通用连接池把连接数量硬限制住。连接池提前建好固定数量的连接,请求来了从池里取,用完归还,从根本上杜绝溢出。
下面是基于SwooleConnectionPool的简单用法:
<?php
use SwooleCoroutineMySQL;
use SwooleConnectionPool;
$pool = new Pool(function () {
$mysql = new MySQL();
$mysql->connect([
'host' => '127.0.0.1',
'user' => 'root',
'password' => 'root',
'database' => 'test'
]);
return $mysql;
}, 20); // 最多20个连接
SwooleCoroutinerun(function () use ($pool) {
$conn = $pool->get();
$res = $conn->query('SELECT 1');
var_dump($res);
$pool->put($conn);
});
该方式适合高并发API,但要求代码全协程化。phpEnv用户需在软件管理里勾选Swoole并重启服务。
3. 调整MySQL与服务端参数
除了代码层,phpEnv自带的MySQL配置也能改。把my.ini里的max_connections调到300,同时把wait_timeout降到60秒,让Sleep连接快速回收。注意这仅是扩容缓解,不解决根本创建频率问题。
| 参数 | 默认值 | 建议值 | 作用 |
|---|---|---|---|
| max_connections | 151 | 300 | 提高上限 |
| wait_timeout | 28800 | 60 | 加速回收 |
三、常见误区与避坑
有人以为脚本结束连接就马上没了,其实PHP的垃圾回收和MySQL的服务端超时是两套逻辑。还有人用单例模式封装数据库类,却在每次getInstance都new PDO,这等于没复用。正确做法是把PDO实例存在静态属性,或者直接用上述连接池。
避坑要点:在phpEnv中做压测时,用ab或wrk打几百请求,再SHOW STATUS LIKE 'Threads_connected',若数值持续等于max_connections,说明没管控住。
综合来看,传统FPM项目用PDO持久化加参数调优最稳;如果是Swoole或WebSocket服务,必须用连接池。按本文步骤在phpEnv里落地,本地再也不会出现连接数溢出中断开发的情况。