预加载需显式声明所有关联关系,漏写、字段未限制、时机错位或嵌套路径错误均会导致N+1;with()用于查询前声明,load()用于查后补载,二者不可混用。

直接用 with() 预加载就能解决绝大多数 N+1 查询问题,但关键不在“会不会用”,而在“怎么用对”——预加载关系写漏、字段没限制、时机错位、嵌套写错,都可能让优化失效甚至更慢。
必须显式声明所有要访问的关联
Eloquent 不会自动推导或连带加载。只要你在循环或模板里写了 $post->user->avatar 或 $user->posts->first()->category->name,而对应关系没在 with() 里完整写出,N+1 就已发生。
- belongsTo / hasOne / hasMany 这三类最常被漏掉,比如 Post → User 必须显式写
with('user') - 嵌套关系要写全路径:
with('user.profile')才有效;with(['user', 'profile'])会忽略profile(除非 Profile 直属 Post) - 多对多或深层嵌套如
posts.comments.author.profile容易引发数据膨胀,建议拆成两步或改用load()分批处理
只查需要的字段,别拉整张表
预加载不等于性能提升——如果 with('posts') 默认执行 SELECT *,关联表字段越多、记录越长,网络传输和内存开销就越大。
- 主查询也加
select():例如User::select('id', 'name')->with('posts')->get() - 关联查询中必须包含外键字段,否则 Eloquent 映射失败,例如
with(['posts' => fn($q) => $q->select('id', 'title', 'user_id')]) - 避免在 Blade 模板里临时调用关系,比如
{{ $post->user->avatar }}—— 这里若没预加载user,N+1 已触发
用 withCount() / withSum() 替代全量关联
只需要数字,就别拉数据。用 with('comments') 获取全部评论来算数量,比 withCount('comments') 多出几十倍内存和网络负载。
-
Post::withCount('comments')->get()返回每个模型的comments_count属性 - 支持条件统计:
withCount(['comments as approved_count' => fn($q) => $q->where('approved', true)]) - 类似还有
withSum('comments', 'likes')、withAvg('ratings', 'score'),但复杂过滤仍需selectRaw + join
区分 with() 和 load() 的使用阶段
with() 是查询前声明,load() 是查后补载——二者底层性能一致,但适用场景不同,混用会失效。
-
with()用于明确知道需要哪些关联的场景,例如列表页:Post::with('user', 'category')->get() -
load()只对已存在的模型集合有效,比如先查$posts = Post::where('status', 'draft')->get(),再按权限决定是否$posts->load('comments') -
$posts->with('comments')是无效写法,Eloquent 静默忽略;必须用$posts->load('comments')


















