php原生不支持数据库连接负载均衡,必须在应用层手动实现轮询逻辑;pdo或mysqli不支持多host逗号分隔,需用数组配置+取模/随机选节点,读操作可轮询从库,写操作必须直连主库。

PHP原生不支持数据库连接负载均衡,必须自己实现轮询逻辑
PHP的PDO或mysqli扩展本身没有内置多主/多节点连接池或自动轮询机制。所谓“负载均衡”,在PHP这种无状态、短生命周期的脚本环境中,只能靠应用层手动选IP、建连接——本质是客户端轮询,不是代理层或中间件级别的均衡。
常见错误是试图在PDO::__construct()里传入多个host,比如"host=192.168.1.10,192.168.1.11;port=3306",这会直接报错SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed,因为MySQL协议根本不接受逗号分隔的host列表。
- 轮询必须在连接前完成:从配置数组中按策略(如取模、随机、轮转)选出一个
$host和$port - 每次请求都应独立选节点,不能复用上一次的连接(除非你做了连接池,但PHP-FPM下极难可靠实现)
- 必须处理节点不可用时的降级:比如当前选中的IP连不上,要跳到下一个,而不是直接抛异常
用数组配置+取模轮询是最简单可靠的方案
适合读多写少、各节点数据强一致(如MHA/VIP后端多从库)的场景。核心是把连接参数存成数组,用$_SERVER['REQUEST_TIME_FLOAT']或microtime(true)生成种子,避免并发请求总打到同一节点。
$db_configs = [
['host' => '192.168.1.10', 'port' => 3306, 'user' => 'app', 'pass' => 'xxx'],
['host' => '192.168.1.11', 'port' => 3306, 'user' => 'app', 'pass' => 'xxx'],
['host' => '192.168.1.12', 'port' => 3306, 'user' => 'app', 'pass' => 'xxx'],
];
<p>$index = (int) (fmod(microtime(true) * 100, count($db_configs)));
$config = $db_configs[$index];</p><p>try {
$pdo = new PDO(
"mysql:host={$config['host']};port={$config['port']};dbname=test;charset=utf8mb4",
$config['user'],
$config['pass'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
} catch (PDOException $e) {
// 连接失败:记录日志,尝试下一个节点(可选)
error_log("DB connect failed to {$config['host']}: " . $e->getMessage());
// 此处可递归或循环重试其他配置
}</p>
- 不用
rand()或mt_rand():它们在FPM子进程中可能被重复seed,导致多个请求落到同一节点 - 避免用
$_SERVER['REQUEST_TIME']:秒级精度太粗,在高并发下容易扎堆 - 如果某节点持续不可用,建议加简单健康检查缓存(如5分钟内失败3次就临时剔除),否则每次都会白试一次
写操作必须绕过轮询,直连固定主库
轮询只适用于只读查询。任何INSERT、UPDATE、DELETE或事务操作,必须明确指定主库地址,否则会造成主从不一致甚至数据丢失。
典型错误是把所有PDO实例都走轮询配置,结果写请求发到了从库,触发SQLSTATE[HY000]: General error: 1290 The MySQL server is running with the --read-only option。
- 分离配置:单独定义
$master_config和$slave_configs数组 - 封装两个工厂函数:
getMasterPdo()和getSlavePdo(),业务代码显式调用 - ORM如Eloquent可通过
connection配置区分mysql-write和mysql-read,底层仍需你提供两套DSN
注意PDO长连接和连接数爆炸风险
开启PDO::ATTR_PERSISTENT在负载均衡场景下非常危险。PHP-FPM进程会把连接绑定到某个节点,后续请求即使轮到别的IP,也复用旧连接,导致“轮询失效”且连接数失控。
- 绝对不要在轮询场景下设
PDO::ATTR_PERSISTENT => true - 每个
PDO实例应在请求结束前显式销毁:$pdo = null;或让其自然离开作用域 - 监控MySQL的
Threads_connected,若发现某节点连接数远高于其他节点,大概率是持久连接没关或轮询逻辑有bug
真正难的不是轮到哪个IP,而是怎么让故障节点快速退出轮询队列、怎么避免写请求误入从库、以及怎么不让连接堆积在某个PHP worker里——这些细节没处理好,轮询反而比单点更脆弱。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











