Hyperf中协程嵌套调用多数据库连接的核心是:连接不混用(须显式声明独立连接名)、上下文不污染(子协程需手动透传连接配置)、事务不跨协程(每协程独立调用transaction且绑定协程ID),连接池参数须按库独立调优。

Hyperf 中协程嵌套调用多数据库连接,核心是三点:连接不混用、上下文不污染、事务不跨协程。不是“能跑就行”,而是每个环节都要对齐协程生命周期。
多连接必须显式声明,禁止隐式复用 default
即使只用两个库(比如业务库 + 日志库),也不能靠 DB::connection() 临时切一次就完事。嵌套调用中若子协程未显式指定连接名,会默认回退到 当前协程绑定的 default 连接——而这个 default 可能已被父协程中途切换过,也可能被其他协程改写。
- 所有模型类必须声明
$connection = 'xxx_db';,不能依赖运行时动态切 - Query Builder 操作一律带连接名:
DB::connection('log_db')->table('logs')->insert(...) - 避免在 service 方法签名里留空参默认用 DB::,例如不要写
public function syncToReport(DB $db),而应写public function syncToReport(ReportDb $db)并通过容器注入
嵌套协程内操作多库,必须手动透传连接上下文
协程 A 启动协程 B 执行报表写入,B 里调用 DB::connection('report_db') 看似没问题,但若 A 曾执行过 DB::purge('report_db') 或触发过连接池重建,B 的连接句柄可能已失效。更危险的是:B 若也开启事务,却没绑定到自己的协程上下文,会和 A 的事务状态冲突。
- 子协程启动前,捕获当前连接配置快照:
$reportConfig = config('databases.connections.report_db'); - 子协程内重新注册临时连接(避免污染全局):
Config::set("databases.connections.report_db_temp", $reportConfig);,再用DB::connection('report_db_temp') - 或更稳妥:用
Connection::make($config)创建全新连接实例,不走连接池缓存(适合低频、强隔离场景)
事务必须与协程 ID 绑定,禁用裸 try/catch 回滚
嵌套中常见错误是父协程开事务,子协程也开同名连接的事务,结果 rollback 只作用于最内层,外层 commit 却把中间脏数据刷进库。Hyperf 的事务管理器不会自动感知嵌套层级。
- 每个需事务的协程,都应独立调用
DB::connection('xxx')->transaction(),而不是共用一个 DB facade - 禁止手写
DB::beginTransaction()+DB::rollback(),这类静态调用无法保证协程上下文隔离 - 若需跨协程事务协同(极少见),必须统一用根协程 ID 做事务键:
Context::set('tx_key', uniqid());,并在各层事务回调中显式传入该 key
连接池参数按库独立调优,不共用 max_connections
业务库和日志库承载压力不同、响应时间不同、MySQL 实例规格也不同。共用一套 pool 配置会导致一个库耗尽连接,另一个库连接闲置,或因 max_connections 设高引发 MySQL 报错 Too many connections。
- 每个连接配置块内必须完整定义
pool子项,包括min_connections、max_connections、wait_timeout - 例如日志库可设
min_connections => 2(保底可用)、max_connections => 50(防突发刷日志);业务库则设min_connections => 5、max_connections => 200 - 检查 MySQL 实例级限制:
show variables like 'max_connections';,确保所有连接池的max_connections总和 ≤ 80% 实例上限


















