根本原因是开发者误用共享资源破坏请求隔离:单例PDO、静态变量、未协程隔离的缓存、跨协程复用ORM实例、缓存键缺失用户维度、多库连接未及时切换,导致不同请求数据交叉污染。

Hyperf 3.2 查询结果“串库”或“串用户数据”,根本原因不是框架本身有 Bug,而是开发者在数据库连接、协程上下文、依赖注入或缓存策略等环节,误用了共享资源,导致不同请求间的数据隔离被破坏。Hyperf 是基于 Swoole 的协程 PHP 框架,其长生命周期和协程并发特性,让传统 PHP-FPM 下“每次请求重置全局状态”的安全假设彻底失效。
下面从几个高频出问题的环节讲清楚:
数据库连接未按请求隔离(最常见)
Hyperf 默认使用 pool 管理 PDO 连接,但若手动创建了单例 PDO 实例(比如用 new PDO() 写在 __construct 或 static $pdo 中),该连接会被多个协程复用,且事务、最后插入 ID、甚至查询结果缓存都可能交叉污染。
- ✅ 正确做法:始终通过
Db::connection()或 DI 容器获取连接,它会自动绑定到当前协程上下文; - ❌ 错误写法:
class UserService { private static $pdo; // 全局静态连接 → 串库! public function __construct() { if (!self::$pdo) self::$pdo = new PDO(...); } }
协程上下文变量未正确绑定
例如,在中间件中把用户 ID 存到 Context::set('uid', $id),但在后续逻辑中没用 Context::get('uid') 读取,而是用了全局变量、静态属性或未加协程隔离的 Cache 实例。
- ✅ 推荐方式:用
Coroutine::getContext()或封装好的ApplicationContext::getContainer()->get(UserContext::class)管理请求级上下文; - ❌ 风险操作:
$this->cache->get('user_profile')若$this->cache是单例且底层用了无协程隔离的Redis连接池(如未配置co模式),也可能返回他人缓存。
ORM 实体或查询构造器被跨协程复用
Hyperf 的 Model 默认是无状态的,但如果你给 Model 加了 public $uid 属性,并在某次请求中赋值,下次协程复用该 Model 实例时就可能残留旧值。
- ✅ 安全做法:避免在 Model 实例上挂载请求相关状态;如需携带上下文,用
with()或闭包参数传递; - ❌ 示例隐患:
$user = User::find(1); $user->uid = $currentUid; // 不安全!该实例可能被其他协程复用 $user->save();
缓存键未包含用户/租户维度
比如用 cache->set('order_list', $data) 存用户订单列表,但没加用户 ID 前缀,所有用户都写/读同一个 key。
- ✅ 必须带上隔离标识:
$key = 'order_list_' . $userId; cache()->set($key, $data, 3600);
多库场景下 connection 切换不及时
使用多数据库时,若用 Db::connection('tenant_a') 查询后,没显式切回默认库,后续调用 Db::table(...) 可能仍在 tenant_a 连接上执行,查出其他租户数据。
- ✅ 显式指定连接:
Db::connection('default')->table('users')->get(); Db::connection('tenant_b')->table('orders')->get(); - ✅ 或用
on()方法临时切换:User::on('tenant_c')->where(...)->get();
本质上,Hyperf 3.2 的“串数据”不是框架缺陷,而是协程模型对老式 PHP 开发习惯的一次强制升级提醒:每个请求必须拥有自己独立的数据通道,不能依赖“脚本结束即销毁”的侥幸心理。只要守住连接、上下文、缓存、ORM 实例这四道关,就不会串。











