with能解决N+1因在主查询阶段预加载关联数据,load是懒加载会触发额外SQL;必须显式声明嵌套关系如'author.profile',且上级需为一对一,传参不可带括号。

Hyperf 的模型关联查询默认不会自动预加载,直接遍历关联数据必然触发 N+1 查询——with 是唯一可靠、开箱即用的解决方式,但用错参数或嵌套层级就等于没用。
为什么 with 能解决 N+1,而 load 不行
with 在主查询阶段就通过 JOIN 或额外 SELECT 预先拉取关联数据,所有结果一次性组装好;load 是懒加载,只在访问关联属性时才发起新查询,遍历 100 条记录就会额外执行 100 次 SQL。
- 必须用
with,不能用load替代 -
with支持链式调用:->with(['author', 'tags']) - 关联关系名必须与模型中定义的函数名完全一致(区分大小写)
- 若关联方法返回
null或未定义,with会静默忽略,不报错但查不到数据
深层嵌套关联必须显式声明,不能靠点号自动递归
Hyperf 的 with 不支持 with('author.profile') 这种写法——它会尝试调用 author() 关联,再在其返回结果上调用 profile(),但前提是 author 关系已加载且是单条模型。实际中更安全的做法是分层指定:
// ✅ 正确:显式声明多级预加载 Post::query()->with(['author', 'author.profile', 'comments.user'])->get(); // ❌ 危险:如果 author 是集合(hasMany),'author.profile' 会失效 Post::query()->with(['author.profile'])->get(); // profile 不会被加载
- 每个嵌套层级都必须是「已定义的关联方法」,且上级关联需为一对一(
hasOne/belongsTo)才可能生效 - 一对多关系(如
comments)后接嵌套(如comments.user)是安全的,Hyperf 会自动处理 - 避免
with(['author.posts.comments'])这类过深路径,极易触发空指针或无限循环
with 传参不写括号,写错格式会导致预加载完全失效
with 接收字符串、数组或闭包,但常见错误是误加括号或混淆语法:
// ❌ 错误:当成函数调用,实际是传参
->with('author()') // 会被当字面量字符串,找不到关联
->with(['author()']) // 同上
// ✅ 正确:纯字符串或数组,不带括号
->with('author')
->with(['author', 'tags'])
->with(['author' => function ($query) {
$query->select(['id', 'name', 'avatar']);
}])
- 闭包里可限制字段、加 where、orderBy,但不能调用
get()或first() - 多个
with连续调用会合并,不是覆盖:->with('a')->with('b')等价于->with(['a', 'b']) - 若关联表字段有歧义(如两个表都有
created_at),需在闭包中用select明确指定别名,否则可能被覆盖
真正容易被忽略的是:预加载只作用于当前查询实例,一旦你后续用 collection->map 或 foreach 手动调用关联属性(比如 $post->author->name),Hyperf 仍会尝试懒加载——哪怕之前用 with 加载过。确保所有关联访问都基于已预加载的数据,而不是重新触发访问器逻辑。


















