Hyperf 2.x Task 进程默认不初始化数据库连接池,导致 ConnectionInterface 注入失败;需在 Task 中手动调用 ConnectionFactory::create() 并绑定到容器,或通过 ensureDb() 懒加载确保每次 execute 前可用。

Hyperf 2.x 的 Task 进程默认不加载数据库组件,直接在 Task::execute() 中调用 $this->db 或注入 ConnectionInterface 会报 DI 找不到服务——这不是配置漏了,而是 Task 进程启动时没初始化数据库连接池。
为什么 Task 进程里 DB 注入失败?
Hyperf 2.x 的 Task Worker 是独立于 HTTP/WS Worker 的子进程,它只加载基础容器和显式注册的组件。数据库连接池(Hyperf\Database\ConnectionInterface)默认只在主协程上下文初始化,Task 进程启动时不触发 DatabaseFactory,所以即使你在 dependencies.php 里绑定了接口,实例也从未被创建。
- 现象:
PHP Fatal error: Uncaught Hyperf\Di\Exception\NotFoundException: Class Hyperf\Database\ConnectionInterface does not exist - 验证方式:在 Task 类的
__construct()里var_dump($this->container->has(ConnectionInterface::class)),结果为false - 注意:Hyperf 3.x 已修复此问题,默认启用全组件加载;2.x 必须手动补全
手动注入 TaskExecutor 并初始化 DB 连接池
核心思路是:在 Task 启动前,主动调用数据库工厂创建连接池实例,并绑定到容器。不能依赖注解自动注入,得自己“把轮子装上”。
- 在
app/Task/YourTask.php中,不要直接写public function __construct(protected ConnectionInterface $db) - 改用构造器内手动获取容器并初始化:
use Hyperf\Database\ConnectionInterface;
use Hyperf\Database\ConnectionFactory;
public function __construct()
{
$container = $this->container;
// 检查是否已存在,避免重复初始化
if (! $container->has(ConnectionInterface::class)) {
$factory = $container->get(ConnectionFactory::class);
$connection = $factory->create('default'); // 'default' 对应 config/autoload/database.php 中的 default 配置
$container->set(ConnectionInterface::class, $connection);
}
}
- 后续在
execute()中可安全使用$this->container->get(ConnectionInterface::class) - 若需多个库,对每个配置名(如
'log')重复调用$factory->create('log')并单独绑定
更稳妥的做法:复用主进程已建好的连接池
Task 进程无法直接复用主进程的连接池对象(跨进程内存不共享),但可以复用配置 + 懒创建。推荐封装一个初始化方法,在每次 execute() 开头调用,避免构造器里初始化失败导致整个 Task 失效:
- 加一个私有方法:
private function ensureDb(): ConnectionInterface
{
$container = $this->container;
if ($container->has(ConnectionInterface::class)) {
return $container->get(ConnectionInterface::class);
}
$factory = $container->get(ConnectionFactory::class);
$connection = $factory->create('default');
$container->set(ConnectionInterface::class, $connection);
return $connection;
}
- 在
execute()中第一行写:$db = $this->ensureDb(); - 这样即使 Task 进程中途崩溃重启,下次执行仍能重建连接池,比构造器一次性初始化更健壮
容易忽略的坑:事务与连接生命周期
Task 进程中开启的事务不会跨请求、也不能提交到主进程上下文。务必注意:
- 不要在 Task 里调用
$db->beginTransaction()后指望 HTTP 请求里的代码能感知或回滚它 - 每个 Task 执行是完全隔离的,
$db实例只在当前 Task 生命周期有效 - 如果用了连接池,Task 结束后连接会自动归还,无需手动
close() - 高频 Task 建议限制并发数(
task_worker_num配置),否则可能耗尽数据库最大连接数
真正麻烦的不是“怎么连上”,而是连上之后误以为它和 HTTP 请求里的 DB 是同一个上下文——Task 是独立进程,所有资源都得自己重新拉起、自己管好生命周期。


















