thinkphp本身不处理跨节点负载均衡,必须依赖proxysql等中间件实现读写分离与轮询分发;其数据库配置需指向proxysql地址(如192.168.1.100:6033)及proxysql定义的前端用户,而非直连mysql;proxysql依据后端mysql的read_only状态自动归类主从节点,并通过mysql_replication_hostgroups启用读组轮询,同时需配置事务相关sql规则(begin/commit/rollback)强制路由至主库组,确保一致性。

ThinkPHP 应用本身不处理跨节点的负载均衡逻辑,必须把读写分离和轮询分发交给 ProxySQL 这类中间件来做;直接改 TP 的数据库配置或重写 getReadConnection 只能影响单请求内的连接选择,无法实现真实流量级的轮询与故障自动剔除。
ThinkPHP 连接地址必须指向 ProxySQL,不能直连 MySQL
TP 的 database.php 配置中,hostname 必须设为 ProxySQL 所在服务器 IP(比如 192.168.1.100),端口是 ProxySQL 的对外服务端口(默认 6033),而不是 MySQL 的 3306。用户名/密码填的是 ProxySQL 中定义的前端用户(如 app_user),不是后端 MySQL 的账号。
常见错误现象:Db::name('user')->select() 报错 Access denied for user,其实是 TP 尝试用 MySQL 账号连了 ProxySQL 的 6033 端口——ProxySQL 默认只认它自己 mysql_users 表里配的用户。
- ProxySQL 管理端(6032)执行:
INSERT INTO mysql_users (username,password,active,default_hostgroup) VALUES ('app_user','*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9',1,1); - 别忘了
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK; - TP 配置示例:
'hostname' => '192.168.1.100', 'hostport' => 6033, 'username' => 'app_user', 'password' => 'xxx'
ProxySQL 必须按 hostgroup 分组管理主从,并启用 read_only 检测
ProxySQL 不靠 SQL 文本判断读写,而是依赖后端 MySQL 实例的 read_only 状态自动归类。你得先把主库设为 read_only=0、所有从库设为 read_only=1,再让 ProxySQL 扫描发现它们。
否则即使写了 SELECT,也可能被路由到主库(如果 ProxySQL 没识别出哪个是只读节点);或者更糟:把 INSERT 发到了从库,报错 The MySQL server is running with the --read-only option。
- ProxySQL 中添加后端节点(用
mysql_servers表):INSERT INTO mysql_servers(hostgroup_id,hostname,port,weight,max_connections) VALUES (0,'192.168.1.101',3306,1000,1000),(1,'192.168.1.102',3306,100,1000),(1,'192.168.1.103',3306,100,1000); - 执行
SELECT * FROM monitor.mysql_server_read_only_log;确认 ProxySQL 已成功探测到各节点的read_only值 -
hostgroup_id = 0是写组,= 1是读组;权重值影响轮询概率,不是严格顺序
读请求轮询需显式配置 read_hostgroup 并启用负载均衡模式
仅靠 mysql_servers 分组还不够。ProxySQL 默认对同一 hostgroup 内多个节点使用「随机」而非「轮询」。要启用轮询,必须在 mysql_replication_hostgroups 表中设置 read_hostgroup,并确保该 hostgroup 的 max_replication_lag 合理(比如 10 秒),否则延迟超限的从库会被自动下线。
常见错误现象:明明加了三台从库,但 SHOW PROXYSQL PROCESSLIST 显示 90% 查询都打在第一台,因为没开轮询,ProxySQL 默认用哈希做连接复用,导致连接长期黏在某一台。
- 启用读组轮询:
INSERT INTO mysql_replication_hostgroups (writer_hostgroup,reader_hostgroup,comment) VALUES (0,1,'tp_app_read_group'); - 触发自动发现:
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK; - 验证轮询生效:
SELECT hostgroup,srv_host,status,ConnUsed,Queries FROM stats_mysql_connection_pool WHERE hostgroup IN (0,1);查看各节点Queries是否趋近均等
TP 事务内所有查询必须强制走主库,ProxySQL 无法自动识别
ProxySQL 的读写分离基于语句类型(SELECT vs INSERT/UPDATE/DELETE)和事务状态(in_transaction 字段)。但它不会解析 PHP 应用是否开启了事务——它只看客户端发来的 SQL 包是否带 BEGIN 或处于活跃事务上下文。
ThinkPHP 的 Db::transaction() 底层是先发 BEGIN,再发业务 SQL,最后 COMMIT。只要这期间所有 SQL 都走同一个连接,ProxySQL 就能正确维持路由一致性。但如果 TP 因异常重试或手动切换连接,就可能把后续 SELECT 发到从库,查到旧数据。
- 最稳做法:TP 中显式用
Db::connect(['read_master' => true])强制事务内全部走主库 - ProxySQL 侧可加规则兜底:
INSERT INTO mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply) VALUES (10,1,'^BEGIN',0,1),(11,1,'^COMMIT',0,1),(12,1,'^ROLLBACK',0,1); - 注意:这些规则必须在
mysql_query_rules中排在更宽泛的SELECT规则之前,否则会被提前匹配跳过
真正难的不是配通,而是让 ProxySQL 在从库宕机时 3 秒内感知、剔除、恢复;以及当主从延迟突增到 30 秒时,不让用户查到明显过期的数据——这两点没法靠改 TP 配置解决,得进 ProxySQL 的 monitor 模块调参,比如 mysql-monitor_connect_timeout 和 mysql-monitor_read_only_interval。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











