一、为什么需要优化MySQL连接
PHP与MySQL是Web开发中最常见的技术组合。在Linux服务器上,如果连接配置不当,可能导致:
- 页面响应缓慢,用户体验差
- 数据库连接数耗尽,服务崩溃
- 服务器资源浪费,成本增加
- 高并发场景下连接池饱和
合理优化MySQL连接配置是提升Web应用性能的关键环节。
二、PHP连接MySQL的常见方式
2.1 mysql扩展(已废弃)
// 已废弃,不推荐使用
$link = mysql_connect('localhost', 'user', 'password');
mysql_select_db('test', $link);
2.2 mysqli扩展
<?php
// 面向过程风格
$conn = mysqli_connect('localhost', 'user', 'password', 'testdb');
if (!$conn) {
die('连接失败: ' . mysqli_connect_error());
}
// 面向对象风格
$mysqli = new mysqli('localhost', 'user', 'password', 'testdb');
if ($mysqli->connect_error) {
die('连接失败: ' . $mysqli->connect_error);
}
?>
2.3 PDO扩展(推荐)
<?php
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=testdb;charset=utf8mb4',
'username',
'password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
die('连接失败: ' . $e->getMessage());
}
?>
三、MySQL服务器端配置优化
3.1 调整最大连接数
编辑MySQL配置文件:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
添加或修改以下参数:
[mysqld]
max_connections = 500
max_connect_errors = 1000
wait_timeout = 600
interactive_timeout = 600
重启MySQL服务:
sudo systemctl restart mysql
3.2 优化连接缓冲区
[mysqld]
thread_cache_size = 64
table_open_cache = 2000
query_cache_size = 64M
query_cache_type = 1
3.3 调整超时参数
[mysqld]
connect_timeout = 10
net_read_timeout = 30
net_write_timeout = 60
四、PHP连接参数优化
4.1 使用持久连接
持久连接可以减少连接建立开销:
<?php
// mysqli持久连接
$mysqli = new mysqli('p:localhost', 'user', 'password', 'testdb');
// PDO持久连接
$pdo = new PDO(
'mysql:host=localhost;dbname=testdb',
'username',
'password',
[
PDO::ATTR_PERSISTENT => true,
]
);
?>
注意:持久连接可能导致连接泄漏,需要谨慎使用。
4.2 连接字符集设置
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=testdb;charset=utf8mb4',
'username',
'password'
);
// 或在连接后设置
$pdo->exec("SET NAMES utf8mb4");
$pdo->exec("SET CHARACTER SET utf8mb4");
?>
4.3 连接缓冲设置
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=testdb',
'username',
'password',
[
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION sql_mode='STRICT_TRANS_TABLES'",
]
);
?>
五、使用连接池
5.1 使用Swoole连接池
在Linux上安装Swoole扩展:
# Ubuntu/Debian
sudo apt install php-swoole
# 或使用pecl
pecl install swoole
PHP连接池示例:
<?php
use Swoole\Database\PDOPool;
use Swoole\Database\PDOConfig;
use Swoole\Runtime;
Runtime::enableCoroutine();
$pool = new PDOPool(
(new PDOConfig())
->withHost('localhost')
->withPort(3306)
->withDbName('testdb')
->withCharset('utf8mb4')
->withUsername('username')
->withPassword('password'),
64 // 连接池大小
);
// 获取连接
$pdo = $pool->get();
$result = $pdo->query("SELECT * FROM users")->fetchAll();
// 归还连接
$pool->put($pdo);
?>
5.2 使用Workerman连接池
<?php
use Workerman\Connection\MysqlConnection;
$connection = new MysqlConnection(
'localhost',
3306,
'username',
'password',
'testdb'
);
$connection->query("SELECT * FROM users");
?>
六、查询优化减少连接占用
6.1 使用预处理语句
预处理语句可以提高查询效率:
<?php
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $userId]);
$user = $stmt->fetch();
?>
6.2 批量查询优化
减少多次查询:
<?php
// 不好的方式:多次查询
foreach ($userIds as $id) {
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$users[] = $stmt->fetch();
}
// 好的方式:批量查询
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($userIds);
$users = $stmt->fetchAll();
?>
6.3 事务优化
<?php
try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO orders (user_id, total) VALUES (1, 100)");
$pdo->exec("INSERT INTO order_items (order_id, product_id, qty) VALUES (LAST_INSERT_ID(), 1, 2)");
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
throw $e;
}
?>
七、连接状态监控
7.1 查看连接状态
<?php
// 查看MySQL连接数
$result = $pdo->query("SHOW STATUS LIKE 'Threads_connected'");
echo "当前连接数: " . $result->fetch()['Value'];
// 查看最大连接数
$result = $pdo->query("SHOW VARIABLES LIKE 'max_connections'");
echo "最大连接数: " . $result->fetch()['Value'];
// 查看连接使用率
$result = $pdo->query("
SELECT
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Threads_connected')
/
(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_VARIABLES WHERE VARIABLE_NAME='max_connections')
* 100 AS connection_usage
");
echo "连接使用率: " . $result->fetch()['connection_usage'] . "%";
?>
7.2 连接监控脚本
#!/bin/bash
# /usr/local/bin/mysql-connection-monitor.sh
MAX_CONN=$(mysql -u root -p'password' -N -e "SHOW VARIABLES LIKE 'max_connections';" | awk '{print $2}')
USED_CONN=$(mysql -u root -p'password' -N -e "SHOW STATUS LIKE 'Threads_connected';" | awk '{print $2}')
USAGE=$(echo "scale=2; $USED_CONN / $MAX_CONN * 100" | bc)
echo "$(date '+%Y-%m-%d %H:%M:%S') MySQL连接数: $USED_CONN / $MAX_CONN 使用率: ${USAGE}%"
if [ $(echo "$USAGE > 80" | bc) -eq 1 ]; then
echo "MySQL连接告警:使用率${USAGE}%" | mail -s "MySQL连接告警" admin@example.com
fi
添加到cron:
sudo crontab -e
# 添加
*/5 * * * * /usr/local/bin/mysql-connection-monitor.sh >> /var/log/mysql-connection.log 2>&1
7.3 使用Prometheus监控
PHP导出监控指标:
<?php
require 'vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
$registry = new CollectorRegistry(new Prometheus\Storage\APC());
$gauge = $registry->getOrRegisterGauge(
'mysql',
'connections_usage',
'MySQL连接使用率'
);
// 计算连接使用率并设置指标
$gauge->set($usagePercent);
header('Content-Type: text/plain');
echo (new RenderTextFormat())->render($registry->getMetricFamilySamples());
?>
八、常见问题与解决
8.1 MySQL server has gone away
连接超时断开:
<?php
// 增加超时时间
$pdo->exec("SET wait_timeout = 28800");
// 或使用重连机制
function getConnection() {
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
} catch (PDOException $e) {
// 等待后重试
sleep(1);
return getConnection();
}
}
?>
8.2 Too many connections
连接数耗尽:
<?php
// 确保关闭连接
$pdo = null;
// 使用try-finally保证资源释放
function executeQuery($sql, $params) {
$pdo = getConnection();
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
} finally {
$pdo = null; // 释放连接
}
}
?>
8.3 连接泄漏排查
<?php
// 记录连接创建和销毁
class ConnectionTracker {
private static $connections = [];
public static function track($pdo, $trace) {
$id = spl_object_id($pdo);
self::$connections[$id] = [
'time' => time(),
'trace' => $trace
];
}
public static function untrack($pdo) {
$id = spl_object_id($pdo);
unset(self::$connections[$id]);
}
public static function report() {
foreach (self::$connections as $id => $info) {
if (time() - $info['time'] > 60) {
error_log("可能连接泄漏: $id, trace: " . $info['trace']);
}
}
}
}
// 定期执行报告
register_shutdown_function([ConnectionTracker::class, 'report']);
?>
九、性能基准测试
9.1 连接性能测试
<?php
// 测试直接连接性能
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'pass');
$pdo = null;
}
echo "直接连接1000次耗时: " . (microtime(true) - $start) . "秒\n";
// 测试持久连接性能
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$pdo = new PDO(
'mysql:host=localhost;dbname=testdb',
'user',
'pass',
[PDO::ATTR_PERSISTENT => true]
);
$pdo = null;
}
echo "持久连接1000次耗时: " . (microtime(true) - $start) . "秒\n";
?>
9.2 连接池性能测试
<?php
use Swoole\Database\PDOPool;
use Swoole\Database\PDOConfig;
$pool = new PDOPool(
(new PDOConfig())
->withHost('localhost')
->withDbName('testdb')
->withUsername('user')
->withPassword('pass'),
64
);
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$pdo = $pool->get();
$pdo->query("SELECT 1");
$pool->put($pdo);
}
echo "连接池1000次耗时: " . (microtime(true) - $start) . "秒\n";
?>
十、最佳实践总结
10.1 配置优化检查清单
- [ ] MySQL max_connections设置为预期并发数的1.5倍
- [ ] thread_cache_size根据连接频率调整
- [ ] wait_timeout设置为合理值(如600秒)
- [ ] PHP使用PDO扩展连接MySQL
- [ ] 字符集统一设置为utf8mb4
- [ ] 使用预处理语句防止注入
- [ ] 高并发场景使用连接池
- [ ] 部署连接数监控告警
- [ ] 定期检查连接泄漏
- [ ] 批量操作使用事务减少连接占用
10.2 不同场景推荐配置
| 场景 | max_connections | 连接方式 | 建议扩展 |
|---|---|---|---|
| 小型网站 | 100-200 | PDO直接连接 | mysqli |
| 中型应用 | 200-400 | PDO持久连接 | PDO |
| 高并发服务 | 400-1000 | Swoole连接池 | Swoole |
| 微服务架构 | 动态配置 | 服务连接池 | Workerman |
10.3 监控指标阈值
| 指标 | 正常范围 | 告警阈值 | 处理建议 |
|---|---|---|---|
| 连接使用率 | < 70% | > 80% | 增加max_connections |
| 连接等待时间 | < 1秒 | > 5秒 | 检查慢查询 |
| 活跃连接数 | < 100 | > 200 | 排查连接泄漏 |
| 连接错误数 | 0 | > 10 | 检查网络或认证 |
结论
在Linux上优化PHP与MySQL的连接配置需要综合考虑:
- 合理设置MySQL服务器参数
- 使用PDO扩展并配置持久连接
- 高并发场景部署连接池
- 优化查询减少连接占用时间
- 建立完善的监控告警体系
通过以上措施,可以显著提升Web应用的数据库性能和稳定性。
注:本文基于PHP 8.2、MySQL 8.0、Ubuntu 22.04编写。