Model::setConnection() 不适用于读写分离,因其强制指定连接名而绕过自动路由逻辑,导致写操作可能发往从库、丢失 sticky 能力、破坏主从一致性;正确方式是严格按 Hyperf 3.0 要求在 database.php 中配置 write 和嵌套数组形式的 read 结构,实现透明路由。

Hyperf 3.0 中 Model::setConnection() 不能用于读写分离路由,它只会强制指定连接名,完全绕过读写分离逻辑。真要实现读写分离,必须依赖配置层的 read/write 结构和底层自动路由机制。
为什么 setConnection() 不适用于读写分离
调用 User::setConnection('slave1') 或 Db::connection('slave1') 会跳过 Hyperf 的读写判断流程,直接使用指定连接——这意味着:
- 写操作仍可能发到从库(违反主从一致性)
- 无法享受
sticky带来的主库读兜底能力 - 手动管理连接名易出错,且与事务、查询构造器等行为耦合度高
- 所有
where、update、save操作都失去自动识别读/写语义的能力
正确配置 read/write 分离结构(databases.php)
Hyperf 3.0 要求将主库和从库明确声明为 write 和 read 数组,而非平级连接名。错误示例:'slave1' 单独作为连接名配置是无效的。
必须这样写:
'default' => [
'driver' => 'mysql',
'write' => [
'host' => env('DB_HOST', '10.0.1.10'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'hyperf'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
],
'read' => [
[
'host' => env('DB_SLAVE1_HOST', '10.0.1.11'),
'port' => env('DB_SLAVE1_PORT', 3306),
'username' => env('DB_SLAVE_USER', 'readonly'),
'password' => env('DB_SLAVE_PASS', ''),
],
[
'host' => env('DB_SLAVE2_HOST', '10.0.1.12'),
'port' => env('DB_SLAVE2_PORT', 3306),
'username' => env('DB_SLAVE_USER', 'readonly'),
'password' => env('DB_SLAVE_PASS', ''),
],
],
'sticky' => true,
'pool' => [/* ... */],
],
-
write必须是单个数组(即使只配一个主库),不能是字符串或对象 -
read必须是数组的数组,每个子数组代表一个从库实例 -
sticky设为true后,当前请求中一旦执行过写操作,后续find、get等读操作会自动 fallback 到主库,防止主从延迟导致查不到新数据
Model 层透明使用:不写任何路由逻辑
只要配置正确,Eloquent Model 完全无需干预读写判断:
namespace App\Model;
<p>use Hyperf\DbConnection\Model\Model;</p><p>class User extends Model
{
protected $table = 'users';
// 不需要 setConnection(),也不需要重写 newQuery()
}
</p>
以下调用自动生效:
-
User::find(123)→ 走某个从库(轮询) -
User::where('status', 1)->get()→ 走从库 -
User::create([...])→ 走主库 -
$user->update([...])→ 走主库 - 同一请求中,
User::create()后再User::find(123)→ 仍走主库(sticky生效)
需要手动切库的唯一合理场景:跨库 join 或特殊分析查询
当必须在从库上执行带 FOR UPDATE 的查询,或需连接非默认库的报表库时,才用 Db::connection():
// 显式走报表库(非读写分离体系内)
Db::connection('report_db')->table('daily_stats')->where('date', today())->first();
<p>// 强制走主库做一致性校验(绕过 sticky 也可行,但慎用)
Db::connection('default')->table('users')->whereRaw('id IN (SELECT user_id FROM orders WHERE status = ?)', [1])->get();
</p>
- 这种用法是例外,不是读写分离的实现方式
- 所有涉及业务核心读写的 Model 操作,应保持“零配置”状态
- 如果发现大量地方要用
connection(),说明配置结构或架构设计有问题
最容易被忽略的是 write 和 read 的嵌套层级:Hyperf 3.0 不接受扁平化多连接名 + 手动路由的旧模式,它要求你把主从关系“声明”进配置结构里,而不是“调度”进代码里。一旦写成 'slave1' 这类独立连接名,读写分离就彻底失效了。











