Hyperf中读写分离需显式指定连接池:Model用on()或$connection属性、关联查询需逐层指定、原生查询须先Db::connection()再链式调用,事务仅限单池,跨池会报错。

Hyperf 3.1项目中需支撑日均千万级订单查询,主库写压力已趋饱和,必须通过读写分离分摊从库流量,但配置后发现部分查询仍走主库、事务报错、关联查询失效——这说明连接池未按语义正确分配,不是加几个配置就能生效。
配置多个独立数据库连接池
在 config/autoload/database.php 中定义至少两个连接池,名称必须全小写、不含点号或大写字母,否则 Db::connection('read.pool') 会抛出 Connection [read.pool] not found 错误。
写库连接命名为 db.write,显式指定 host、port、database、username 等全部参数,不可复用 default 的配置块;读库单独起名如 read_pool_1,建议配置 strict_type => false 和 fetch_mode => PDO::FETCH_ASSOC,避免从库类型转换开销。
default 连接池保留不用,防止业务代码未显式指定时意外命中主库造成从库写入失败。
Model 层强制走读库的三种写法
方法一:链式调用 on() 方法(最常用)
User::on('read_pool_1')->where('status', 1)->get() 是有效写法;但 User::on('read_pool_1')->save() 会失败,因为从库通常启用 --read-only 选项,触发 SQLSTATE[HY000]: General error: 1290 报错。
方法二:模型类内声明 $connection 属性
在 User 模型顶部添加 protected $connection = 'read_pool_1'; 注意该设置仅对 find()、all()、first() 等静态方法生效,对 new User()->save() 实例方法无效。
方法三:配合 with() 做关联读路由
关联查询默认仍走 default 连接,必须对每个关联模型也显式指定:User::on('read_pool_1')->with(['posts' => fn ($q) => $q->on('read_pool_1')])。漏掉任一环节,posts 表就会回退到主库查询。
原生查询与事务的连接绑定规则
第一步:Db::table('users') 默认永远走 default 连接,哪怕你刚在 Model 中调用过 on('read_pool_1') —— 二者完全隔离,无上下文继承关系。
第二步:必须先 Db::connection('read_pool_1'),再链式调用 table()、where()、first() 等方法,顺序不可颠倒。
第三步:Db::select() 不接受连接名参数,必须前置 connection();而事务 Db::transaction() 只能绑定单个连接池,【跨 read_pool_1 和 db.write 的事务会直接报错】,MySQL 从库不支持 BEGIN,且分布式事务在 Hyperf 中无自动协调机制。
验证连接池是否真正生效
启用 SQL 日志:在 database 配置中开启 'log_queries' => true,并在 .env 中设置 DB_LOG=true。
执行一条带 on() 的查询,检查日志中出现的 host 是否为你配置的 read_pool_1 的 IP 地址,而非 default 的 localhost。
若日志显示 host=localhost 且 port=3306,说明 on() 调用被忽略——大概率是连接池名拼写错误、config 文件未重载、或模型中 $connection 属性值与配置名不一致。


















