本文目录导读:

当 PHP 出现连接池满的情况,通常是因为数据库连接、Redis 连接或其他资源连接没有被正确释放,或者并发量超过了连接池的最大容量,以下是系统的排查和处理方案:
立即应急处理
暂时扩大连接池
// 数据库连接池配置示例 (Laravel)
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
// 增加连接池大小
'pool' => [
'max_connections' => 100, // 调大这个值
'min_connections' => 5,
'wait_timeout' => 5, // 等待时间(秒)
'idle_timeout' => 60,
],
],
],
快速清理失效连接
-- MySQL 查看当前连接 SHOW PROCESSLIST; -- 杀死长时间运行或无效的连接 KILL [connection_id];
# Redis 连接查看 redis-cli CLIENT LIST redis-cli CLIENT KILL <client_id>
根因排查
常见原因定位
// 1. 检查是否有未关闭的连接
public function getUserData() {
$client = new Redis(); // 或 new PDO()
$client->connect('127.0.0.1', 6379);
$data = $client->get('user:data');
// 忘记关闭连接 - 这是常见问题
// $client->close(); // 加上这行
return $data;
}
使用连接池才有的常见坑
// 2. 事务未提交或回滚
$pdo = new PDO(...);
$pdo->beginTransaction();
try {
// ... 业务逻辑
$pdo->commit(); // 忘记提交
} catch (Exception $e) {
$pdo->rollBack(); // 忘记回滚
}
最佳实践解决方案
使用连接池中间件
// 使用 Swoole 或 Hyperf 等框架
use Swoole\Coroutine\Channel;
use Swoole\Coroutine\PostgreSQL;
class ConnectionPool {
private $pool;
private $maxSize;
public function __construct($maxSize = 50) {
$this->pool = new Channel($maxSize);
$this->maxSize = $maxSize;
$this->init();
}
private function init() {
for ($i = 0; $i < $this->maxSize; $i++) {
$this->pool->push($this->createConnection());
}
}
private function createConnection() {
// 创建数据库连接
return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
public function getConnection() {
return $this->pool->pop();
}
public function releaseConnection($conn) {
$this->pool->push($conn);
}
}
使用成熟的连接池库
# 安装 PHP Redis 扩展客户端 (phpredis) composer require predis/predis
// 使用 Predis 连接池
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'connections' => 50, // 连接池大小
'timeout' => 5.0,
'read_write_timeout' => 5.0,
]);
业务层面优化
释放连接的最佳实践
class DatabaseManager {
private static $connections = [];
public static function getConnection($name = 'default') {
if (!isset(self::$connections[$name])) {
self::$connections[$name] = self::createConnection();
}
return self::$connections[$name];
}
public static function closeConnection($name = 'default') {
if (isset(self::$connections[$name])) {
self::$connections[$name]->close();
unset(self::$connections[$name]);
}
}
// 在 finally 块中确保关闭
public static function executeQuery($sql) {
$conn = self::getConnection();
try {
return $conn->query($sql);
} finally {
self::closeConnection();
}
}
}
使用 try-finally 确保释放
public function handleRequest() {
$redis = null;
try {
$redis = $this->getRedisConnection();
// 业务逻辑
$result = $redis->get('key');
return $result;
} catch (Exception $e) {
// 记录日志
throw $e;
} finally {
// 确保释放
if ($redis) {
$redis->close();
}
}
}
运维层面设置
MySQL 调整连接超时
-- 查看当前设置 SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'max_connections'; -- 调整超时时间(秒) SET GLOBAL wait_timeout = 60; SET GLOBAL interactive_timeout = 60; -- 设置最大连接数 SET GLOBAL max_connections = 500;
Redis 配置优化
# redis.conf # 关闭空闲连接 timeout 60 # 设置最大连接数 maxclients 10000
监控与预警
// 添加监控逻辑
class ConnectionMonitor {
public static function checkPoolUsage() {
$connections = $this->getCurrentConnections();
$max = 100; // 最大连接数
if ($connections >= $max * 0.8) {
// 发送告警通知
$this->sendAlert("连接池占用80%");
}
if ($connections >= $max) {
$this->sendAlert("连接池已满,请检查");
}
}
}
长期解决方案
使用更高级的数据库驱动
// 使用 Swoole 模式
use Swoole\Runtime;
use PDO;
Runtime::enableCoroutine();
// 创建连接池
class DatabasePool {
protected $pdo;
public function __construct() {
$this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
public function query($sql) {
return $this->pdo->query($sql);
}
public function close() {
$this->pdo = null;
}
}
使用连接池管理工具
- ProxySQL - 数据库代理层
- PgBouncer - PostgreSQL 连接池
- Redis Proxy - Redis 代理
总结建议
- 短期:调大连接池大小,及时清理无效连接
- 中期:排查代码中的连接泄漏问题,确保正确释放
- 长期:使用连接池中间件,添加监控告警,优化业务代码
关键是要在编码时养成好的习惯:谁创建连接,谁负责关闭,并使用 try-finally 确保释放。