hyperf模型不能直接复用laravel的user::find(1)或new user(),因单例模式会导致静态属性跨请求残留、连接复用污染数据;应声明为transient服务注入或使用db::table()协程安全查询。

Hyperf 模型不能当 Laravel 那样 new 或静态调用
直接写 User::find(1) 或 new User() 在 Hyperf 里能跑,但会污染协程上下文:静态属性(如 $with、$casts)跨请求残留,连接句柄被复用导致查到别人的数据。这不是语法错误,是生命周期错配。
正确做法是把模型声明为 transient 服务,交由容器每次新建:
- 在控制器或服务中用
@Inject(transient=true)注入:private User $user; - 不手动 new,不调用静态方法查数据;改用
$this->user->where(...)->first() - 若需链式查询,用
User::query()->where(...)->first()——query()每次返回新 Builder 实例,安全
DB::table() 是最轻量、最推荐的读取方式
不需要模型类、不触发 boot / casting / accessor,适合简单查询、统计、视图读取等场景。它绕过 Model 生命周期,天然协程安全。
常见用法示例:
- 单条:
Db::table('user')->where('id', 1)->first() - 字段值:
Db::table('user')->where('id', 1)->value('name') - 列数组:
Db::table('user')->pluck('email') - 聚合:
Db::table('order')->where('status', 'paid')->sum('amount') - 存在判断:
Db::table('user')->where('phone', $phone)->exists()
注意:不要在 Db::table() 后加 ->toSql() 调试——它会跳过协程执行路径,导致连接未释放或上下文错乱。
事务必须用 Connection::transaction(),别碰 DB::beginTransaction()
DB::beginTransaction() 是静态门面,不绑定协程上下文。并发下 A 请求开启事务后还没 commit,B 请求可能误用同一连接,造成脏写或死锁。
正确写法只有一种:
- 显式获取连接对象:
$connection = Db::connection(); - 用协程安全事务:
$connection->transaction(function () { /* ... */ }); - 手动控制时,必须成对调用
$connection->beginTransaction()/$connection->commit()/$connection->rollback()
漏写 rollback() 不会自动兜底——Hyperf 没有 PHP-FPM 请求结束时的隐式清理机制。
连接池参数设错会导致“随机失败”,而非明确报错
比如 MySQL 实例最大连接数是 500,但你把 max_connections 设成 800,现象不是“连接池满”,而是部分请求卡在 wait_timeout 后抛出 Connection pool is empty 或直接超时;日志里看不到 Too many connections,排查会绕远路。
建议配置原则:
-
min_connections至少为 1(冷启动首请求不建连) -
max_connections≤ MySQL 的max_connections × 0.8,再按 Worker 数 × 平均并发 × 1.2 反推 -
wait_timeout别设太小(如 0.1),否则高并发下容易因抢不到连接而失败
真正难调试的点在于:问题只在压测或流量高峰时浮现,且表现不稳定——这往往是连接池和 MySQL 实例容量没对齐的信号。











