Hyperf MySQL 负载均衡依赖显式连接池绑定与严格上下文隔离:连接池名须全小写、无点号、仅含下划线(如 read_pool_1);Model 查询必须显式调用 on(),Db::connection() 必须前置;跨池事务禁止,读写池不可混用。

Hyperf 里 MySQL 客户端的负载均衡,不靠“自动识别 SQL 类型”或“透明代理”,而是靠显式连接池绑定 + 严格上下文隔离。配置错一个字母、漏一次 on()、跨池用事务,都会直接报错或走错库。
连接池命名必须全小写且不含点号
Hyperf 的 Db::connection('read.pool') 会直接抛出 Connection [read.pool] not found 错误——不是配置没生效,是名字非法。Hyperf 数据库组件底层用的是字符串键名匹配,不支持点号、大写、下划线(部分版本甚至拒绝下划线)。必须写成 read_pool_1 或 db_read 这类纯小写+下划线组合。
- config/autoload/database.php 中定义连接池时,key 名如
'read_pool_1'必须和代码中on('read_pool_1')完全一致 - 不能复用
default配置块来“继承”参数,每个池必须独立声明 host/port/database/username/password - 建议所有读池启用
strict_type => false和fetch_mode => PDO::FETCH_ASSOC,避免从库因只读模式触发类型转换异常
Model 查询必须显式 on() 或声明 $connection
User::where('id', 1)->get() 永远走 default 池,哪怕你刚在上一行调过 User::on('read_pool_1')->first() —— Hyperf 的 Model 查询没有连接上下文继承机制。
- 链式调用
on()是最安全的方式:User::on('read_pool_1')->with(['posts' => fn($q) => $q->on('read_pool_1')])->get() -
protected $connection = 'read_pool_1'只影响静态查询方法(all()、find()、first()),对new User()->save()无效 - 关联模型(如
posts)默认仍走default,必须逐层指定on(),漏掉一层就回退主库
原生 Db 查询必须先 connection() 再链式调用
Db::table('users')->where('status', 1)->first() 不接受连接名参数,也**不会**继承 Model 的 on() 设置。它永远走 default 池。
- 正确写法只有这一种:
Db::connection('read_pool_1')->table('users')->where('status', 1)->first() -
Db::select()同样不支持传连接名,必须前置connection() - 事务
Db::transaction()只能绑定单个连接池,Db::transaction(function () { User::on('db.write')->save(); Order::on('read_pool_1')->first(); })会直接崩溃,MySQL 从库也不支持BEGIN
事务和跨池操作是硬性禁区
Hyperf 没有分布式事务协调器,MySQL 从库默认开启 --read-only,这两条叠加导致任何试图跨池的事务都会失败,错误通常是 SQLSTATE[HY000]: General error: 1290 或 Cannot execute statement in a READ ONLY transaction。
- 写操作只能在
db.write池中执行,读操作尽量收束到read_pool_x池 - 如果业务逻辑强依赖“查后即写”,不要强行拆到不同池,宁可让读走主库,也别跨池事务
- 健康检查、连接池探活、协程上下文泄漏等问题,往往在高并发压测时才暴露,但根因都在连接池命名或调用顺序上



















