hyperf异步协程切换多库必须显式指定连接名且禁用sticky,事务须与连接强绑定,禁止跨连接事务;模型应设$connection属性而非运行时切换;不同数据库版本/实例须分池配置,异步任务需传参选库而非依赖上下文。

Hyperf 异步协程环境下切换多数据库连接,不是简单调个 Db::connection('xxx') 就完事。协程共享连接池、上下文隔离弱、事务状态残留、驱动协议不兼容——这些都会在高并发异步任务中突然爆发问题。关键不在“能不能切”,而在“切得稳不稳”。
协程内必须显式指定连接,不能依赖默认或单例缓存
异步队列 Job 或定时任务里,Db::connection() 不会自动继承主请求的连接选择。如果模型没声明 $connection,又没在查询时显式调用 connection('log_db'),就会走 default 连接——而这个 default 可能是订单库,不是日志库。
- 所有跨连接操作必须写全:比如
Db::connection('report_db')->table('sales')->sum('amount') - 避免在服务类里缓存连接实例(如
private $db),协程复用会导致连接错乱 - Eloquent 模型优先设
protected $connection = 'log_db',比运行时切换更安全
事务与连接池必须绑定同一连接名,且禁用 sticky
协程池里的连接是复用的,sticky => true 会让事务状态滞留在某个连接上,下次协程拿到它时可能带着未提交的事务或锁,极易引发死锁或数据错乱。
- 在
config/autoload/databases.php中为每个连接显式关闭 sticky:'sticky' => false - 事务块必须和连接名强绑定:
Db::connection('log_db')->transaction(...),不能先切连接再开事务 - 禁止在事务内切换连接名——Hyperf 不支持跨连接事务,会直接报错或行为不可控
不同版本/引擎数据库必须分池,不能共用配置
MySQL 5.7 和 MySQL 8.0 认证方式不同;PostgreSQL 14 和 15 的预编译语句行为有差异;RDS 和自建实例的超时策略也不一致。混在一个连接池里,轻则 handshake 失败,重则连接池卡死。
- 每个独立数据源单独配连接名,例如
mysql_v57_rds、pgsql_v14_cloud - 每个连接必须完整定义
pool参数:min_connections和max_connections按该实例实际承载力设(如 RDS 200 连接上限,池最大设 150) - 驱动级参数要补全:MySQL 8.0 加
'options' => [PDO::ATTR_EMULATE_PREPARES => false],5.7 可能需设为true
异步任务中上下文丢失,用户/租户信息不能靠$this传
Job 类 handle() 方法运行在新协程里,中间件里塞进 $this->tenantId 的值根本拿不到——这是单例对象属性,被多个协程共享修改,结果就是张三的任务查了李四的库。
- 提交任务时把租户标识作为构造参数传入:
new SyncUserCacheJob($userId, $tenantId) - 在 handle() 里用该参数动态选库:
Db::connection("tenant_{$this->tenantId}")->table(...) - 绝对不要在 Job 类里用
Context::get()读主请求上下文——除非你确认框架已透传(Hyperf 默认不透传)











