预加载需按场景、层级、条件合理使用,with()仅基础止损n+1;带条件须用闭包,limit在嵌套预加载中无效,深层分页应改用子查询或分步处理。

预加载不是“加个 with() 就完事”,它得看场景、看层级、看条件,否则容易白写甚至拖慢查询。
with() 基本用法和 N+1 的真实代价
循环里直接访问 $user->posts 会触发 N+1:查 100 个用户,就发 101 条 SQL(1 次 users,100 次 posts)。with('posts') 把关联查合并成 2 条 SQL,这是最基础的止损点。
- 单模型查:用
User::with('posts')->find(1),别用User::find(1)->posts - 集合查:用
User::with('posts')->get(),别在 foreach 里调$user->posts - 嵌套关联:比如用户→组织→事件,写成
with(['organisations.events']),注意中括号和点号不能混
带 where 条件的预加载必须用闭包约束
with('posts') 拿的是全部关联数据;如果只想加载已发布的文章,直接加 where('published', 1) 在外层无效——它只会过滤主模型,不会下推到关联查询里。
- 正确写法:
with(['posts' => function ($q) { $q->where('published', 1); }]) - 多个条件也塞进闭包:
$q->where('published', 1)->orderByDesc('created_at') - 错误写法:
with('posts')->where('published', 1)—— 这是在查 users 表,不是 posts
只取 ID 数组时,pluck() 比全量模型更轻量
如果页面只需要关联模型的 ID 列表(比如多选框、权限校验),加载完整模型纯属浪费内存和序列化开销。
- 推荐:
$user->posts()->pluck('id')或$user->posts()->pluck('id', 'title') - 批量场景可配合查询构建器:
DB::table('posts')->where('user_id', $user->id)->pluck('id') - 避免:
$user->posts->pluck('id')—— 先加载了所有 Post 模型,再取 ID,绕远路
深层嵌套 + 条件 + 分页时,with() 失效,得换策略
比如“查用户,每个用户只取最新 3 篇已发布文章”,with(['posts' => fn($q) => $q->latest()->limit(3)->where('published', 1)]) 是错的:limit 在预加载里不生效,Eloquent 会忽略它。
- 可行方案一:用子查询 + join,例如
select * from users left join (select * from posts where published = 1 order by created_at desc limit 3) as p on p.user_id = users.id - 可行方案二:先查用户,再对每个用户用
posts()->latest()->take(3)->get()—— 仅限用户数极少(≤5) - 关键判断点:当预加载无法表达“每个父模型限定子集数量”时,
with()就该让位给原生查询或分步处理











