本质是laravel不感知拓扑变化,需主动清理连接池、配置正确read/write嵌套结构、启用sticky,并通过safereader封装实现主库可达时的可控读降级。

主从切换时读请求仍打到旧主库,本质是 Laravel 没感知数据库拓扑变化——它只按 config/database.php 里写的地址建连接,不主动探测、不自动重路由。所谓“优化”,不是让框架自动切从库,而是让应用在主库不可用或刚切换后,**把本该发给从库的读请求,安全地、可控地临时回退到新主库(即当前唯一可写节点)**,避免查询失败或读到脏数据。
确保读写分离配置本身健壮
这是所有优化的前提。若 config/database.php 中 mysql 连接没正确嵌套 read/write,Laravel 根本不会走从库,自然也谈不上“切换后读错”。必须满足:
-
read 必须是数组,哪怕只有一个从库也要写成
'read' => [['host' => '192.168.1.10']],不能是'read' => ['host' => '...'] -
write 必须是单元素关联数组,如
'write' => ['host' => '192.168.1.5'],不能是数组或字符串 - 共用配置(database、username、charset 等)必须提到外层,写在 read/write 内部会被忽略
- 启用 sticky => true:事务中或写操作后短时间内,读请求继续走主库,防止主从延迟导致不一致
主库切换后主动刷新连接池
Laravel 的 PDO 连接是长连接复用的。主库 IP 变了,但旧连接还缓存在连接池里,后续请求可能继续用它,直到超时或异常断开。需在切换完成后主动清理:
- 调用
DB::purge('mysql')清空 mysql 连接池实例 - 如果用了连接名别名(如 'mysql_read'),也要对应 purge
- 不要依赖
DB::reconnect(),它不保证重建所有连接;purge + 下次查询自动重建更可靠
读请求降级策略:主库可用时才允许回退
不能一发现从库连不上就盲目切主库读——那样会把读压力全压回主库,失去分离意义。应加一层探测逻辑:
- 封装一个
SafeReader::get($table, $query)方法 - 内部先尝试用
DB::connection('mysql')->table($table)->$query(触发原生读路由) - 捕获 PDOException 或超时异常后,检查主库连通性(例如执行
SELECT 1) - 仅当主库可达且无事务上下文时,才降级为
DB::connection('mysql.write')->table(...)查询 - 记录日志,标记“已降级”,便于监控和容量评估
配合高可用中间件做连接预检
在关键读接口前加中间件,定期或按需验证从库健康状态:
- 定时 ping 所有从库 host + port,更新内存缓存的可用列表
- 若某从库连续失败 N 次,临时从 read 数组中剔除(需动态修改配置或用自定义连接工厂)
- 主库切换完成的信号(如通过 Redis 发布/订阅)到达后,立即触发一次全量探活
- 避免每次查询都做探测,损耗性能











