PHP读写分离需先配置MySQL主从复制,再通过DbConnection类路由读写请求,自动识别SQL类型,增强SELECT判断,支持显式覆盖,并集成健康检测与故障转移。

如果您在PHP项目中使用MySQL主从同步实现读写分离,但应用程序未能正确将读请求分发到从库、写请求分发到主库,则可能是由于数据库连接路由逻辑缺失或配置未生效。以下是实现该架构的具体配置与代码步骤:
一、MySQL主从环境基础配置
确保MySQL主库启用二进制日志并生成唯一server-id,从库配置对应replication用户及CHANGE MASTER语句指向主库,使从库能持续拉取并重放主库binlog事件。只有主从复制链路稳定运行,读写分离才有数据一致性保障。
1、在主库my.cnf中添加:log-bin=mysql-bin 与 server-id=1,重启MySQL服务。
2、在主库执行:CREATE USER 'repl'@'%' IDENTIFIED BY 'replpass'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;
立即学习“PHP免费学习笔记(深入)”;
3、在主库执行:FLUSH TABLES WITH READ LOCK; 记录当前binlog文件名与位置(SHOW MASTER STATUS;)。
4、在从库my.cnf中设置:server-id=2,重启MySQL服务。
5、在从库执行:CHANGE MASTER TO MASTER_HOST='主库IP', MASTER_USER='repl', MASTER_PASSWORD='replpass', MASTER_LOG_FILE='记录的文件名', MASTER_LOG_POS=记录的位置;
6、在从库执行:START SLAVE; 并通过 SHOW SLAVE STATUS\G 确认Slave_IO_Running 和 Slave_SQL_Running 均为Yes。
二、PHP项目中数据库连接池抽象层设计
通过封装统一的DB类,将读操作与写操作路由至不同连接实例,避免业务代码直接操作mysqli或PDO连接,实现解耦与可维护性。
1、定义配置数组,分别指定主库和从库的host、port、username、password、dbname等参数,存于config/database.php中。
2、创建DbConnection类,在构造时根据role参数('master'或'slave')初始化对应PDO实例,并设置ATTR_ERRMODE为EXCEPTION。
3、在DbConnection中实现getMaster()与getSlaves()静态方法:getMaster()返回单个主库PDO对象;getSlaves()返回从库PDO对象数组,支持随机选取一个用于负载均衡。
4、禁止在事务内调用从库读取——需在getSlaves()调用前检查当前是否处于事务中,若已开启事务则强制使用主库连接。
三、读写分离路由逻辑实现
在DAO或Model层拦截SQL语句类型,依据关键词自动判断读/写意图,避免开发者手动指定连接,降低误用风险。
1、编写isWriteQuery($sql)函数:使用preg_match匹配以INSERT|UPDATE|DELETE|REPLACE|ALTER|CREATE|DROP|TRUNCATE|LOAD DATA|CALL开头的SQL(忽略大小写与前置空格/换行)。
2、在通用查询执行方法query($sql, $params = [])中,先调用isWriteQuery($sql),若为true则调用DbConnection::getMaster()->prepare();否则调用DbConnection::getSlaves()[array_rand(DbConnection::getSlaves())]->prepare()。
3、对SELECT语句进一步增强识别:若SQL中包含FOR UPDATE 或 LOCK IN SHARE MODE,强制走主库连接。
4、提供显式覆盖接口:如query($sql, $params, $forceMaster = true),供特殊场景(如刚写入即查)绕过自动路由。
四、Laravel框架下的读写分离集成(基于原生PDO)
不依赖Eloquent内置读写分离,而是通过自定义DB Facade接管底层连接分配,确保底层驱动可控且行为透明。
1、在config/database.php中,为mysql配置项增加read与write子键,分别嵌套host、port等字段,格式与Laravel默认结构一致。
2、创建CustomMySqlConnector类,继承Illuminate\Database\Connectors\MySqlConnector,重写connectUsingReadPdo()方法,使其在读连接时优先从read数组中随机选取配置。
3、在AppServiceProvider的boot()中,使用DB::extend('mysql_custom', function ($config, $database, $prefix, $name) { return new \App\Database\CustomMySqlBuilder($config); });
4、在CustomMySqlBuilder中,复写getPdoForReading()方法,使其调用CustomMySqlConnector::connectUsingReadPdo()并传入read配置组,而非默认的完整$config。
5、验证有效性:在tinker中执行DB::connection('mysql_custom')->select('SELECT @@server_id'),多次调用应返回不同从库的server-id值。
五、连接健康检测与故障转移机制
当某台从库宕机或复制延迟超阈值时,需临时剔除该节点,防止请求失败或读到过期数据,保障服务可用性与一致性边界。
1、在getSlaves()方法中,对每个从库PDO执行SELECT @@slave_sql_running, @@seconds_behind_master,过滤掉SQL线程未运行或延迟超过60秒的实例。
2、建立本地内存缓存(如APCu),存储各从库server-id与其最近一次健康状态(timestamp + status),缓存有效期设为10秒,避免高频探测。
3、若所有从库均被标记为不可用,则降级为全部请求主库,同时记录告警日志:All slaves unavailable, fallback to master for read。
4、每次从库连接失败时(PDOException捕获),立即更新该server-id在APCu中的状态为false,并清除其连接实例引用。
5、在从库恢复后,通过定时脚本或守护进程轮询其SHOW SLAVE STATUS输出,确认Seconds_Behind_Master ≤ 5且Slave_SQL_Running = Yes,再将其状态重置为true。



















