根本原因是laravel默认不支持按权重随机选从库,只支持固定fallback或手动指定;需用闭包动态返回连接名实现权重分流,且须禁用sticky、避免事务干扰读操作。

为什么 read 和 write 连接会走错库?
根本原因不是配置写错了,而是 Laravel 的连接解析逻辑默认不支持「按权重随机选从库」,只支持「固定 fallback」或「手动指定连接名」。你配了多个 read 主机,但没启用 sticky 或没触发读操作上下文,结果所有查询(包括 select)都跑到主库去了。
-
DB::select()、User::query()->get()这类读操作,只有在当前没有活跃写事务时,才会尝试走read连接 - 一旦前面执行过
DB::transaction()或save(),后续读也会被绑定到主连接(除非显式用on('read')) - 配置里把
read写成数组后,Laravel 默认是「轮询」而非「加权随机」——它根本不读取你写的weight字段
怎么让从库真正按权重分流?
Laravel 原生不支持权重分流,必须自己接管 read 连接的选择逻辑。最轻量的做法是在数据库配置中用闭包动态返回连接名,绕过默认的静态数组解析。
- 在
config/database.php的mysql配置里,把read从数组改成一个function()闭包 - 闭包内部用
mt_rand()按权重抽样,返回具体连接名(如'mysql-read-1'、'mysql-read-2') - 每个从库单独定义为独立连接(即
connections.mysql-read-1),并确保它们共享同一套host、database等基础参数 - 别碰
sticky:设为true会强制读写都走主库,设为false才可能触发读分离
示例片段:
'mysql' => [
'read' => function () {
$weights = ['mysql-read-1' => 70, 'mysql-read-2' => 30];
$rand = mt_rand(1, 100);
$sum = 0;
foreach ($weights as $name => $weight) {
$sum += $weight;
if ($rand [...],
'sticky' => false,
],
DB::connection('mysql') 为什么还是连主库?
因为 mysql 是总连接名,Laravel 在第一次调用时会缓存它解析出的读/写连接实例。如果你没触发读操作(比如直接写了 DB::connection('mysql')->table('users')->get()),它就按默认策略选了写连接。
- 显式声明读连接:
DB::connection('mysql-read-1')->table('users')->get()—— 绕过自动分发,但失去权重意义 - 触发自动读分发:用模型或 Query Builder 的标准读方法,且确保前面没开启事务、没执行过写操作
- 检查是否被中间件干扰:某些权限中间件会提前执行
Auth::check(),而该方法内部可能有读用户信息的操作,导致连接被“粘住” - 用
DB::connection()->getPdo()打印当前 PDO 实例的getAttribute(PDO::ATTR_CONNECTION_STATUS),确认实际连的是哪个 host
从库延迟导致数据不一致怎么办?
权重分流本身不解决延迟问题,反而会让问题更隐蔽——你不知道哪次读命中了延迟高的从库。业务上必须接受「最终一致性」,技术上只能做分级控制。
- 强一致性读(如刚写完立刻查):显式用
DB::connection('mysql-write')或加->useWritePdo() - 非关键读(如列表页、统计):放权给权重分流,容忍秒级延迟
- 不要依赖从库的
last_insert_id或@@binlog_gtid_executed类变量——这些在从库上不可靠或无意义 - 监控各从库的
Seconds_Behind_Master,超过阈值时临时从权重池中剔除该节点(需配合服务发现或配置热重载)
权重只是流量分配手段,不是一致性方案。真要保一致,就得收口读路由逻辑,而不是指望框架自动判断哪条 SQL 该去哪台机器。











