必须用 with() 的情况是:从数据库首次查询模型(如 User::all())且需访问其关联数据时,否则会触发 N+1 查询;with() 需在 get()/first() 前调用,通过 JOIN 或额外 SELECT 一次性加载关联。

用 with() 预加载关联数据,能直接避免 N+1 查询;但该用 with() 还是 load(),取决于查询时机和数据来源——前者在主查询前声明,后者对已查出的模型实例补加载。
什么时候必须用 with()?
当你从数据库首次查出模型(比如 User::all() 或 User::where(...)->get()),又需要访问其关联数据(如 $user->posts)时,不加预加载就会触发 N+1:1 次查用户 + N 次查每条用户的 posts。
with() 必须在 get()、first() 等执行方法之前调用,它会改写底层 SQL,用 JOIN 或额外 SELECT 一次性拉取关联数据。
- 支持嵌套预加载:
User::with('posts.comments.author')->get() - 可配合约束闭包过滤关联数据:
User::with(['posts' => function ($q) { $q->where('published', true); }])->get() - 多个关联用数组传入:
User::with(['posts', 'profile'])->get() - 不要在循环里调用
with()—— 它不作用于单个模型,只影响即将执行的查询
为什么有时候得用 load()?
当你已经拿到一个模型或集合(比如从缓存读取、或上一步只查了 ID),但后续才决定要加载关联,这时 with() 已经没机会介入,只能用 load() 对已有实例补查。
load() 是 Eloquent Collection 或单个 Model 实例上的方法,它发起新的查询,但会自动绑定外键条件,避免全表扫描。
- 对单个模型:
$user->load('posts')→ 触发 1 条SELECT * FROM posts WHERE user_id = ? - 对集合:
$users->load('posts')→ 底层自动 IN 查询:WHERE user_id IN (1,2,3,...),仍是 1 次查询 - 同样支持嵌套和闭包约束:
$users->load(['posts.tags' => function ($q) { $q->select('id', 'name'); }]) - 多次调用
load()不会重复查同一关联(Eloquent 内部有加载标记)
常见踩坑点:with() 和 load() 混用导致重复查询
最典型的是:先用 with() 查出模型,之后又对同一个关联调用 load()。Eloquent 不会跳过,而是再发一次查询——因为 load() 不检查是否已预加载。
User::with('posts')->get();
// 后续又写:
$user->load('posts'); // ❌ 多余的一次查询另一个容易忽略的点是动态关联加载:
- 用
$model->relation访问未预加载的关联,会触发懒加载(lazy loading),即 N+1 的根源 - Laravel 9+ 默认禁用懒加载(抛出
Illuminate\Database\LazyLoadingViolationException),但仅限开发环境;生产环境仍静默执行,务必在本地打开DB_LOG_LAZY_LOADING环境变量排查 -
loadMissing()是安全替代:只在关联尚未加载时才查,已加载则跳过
性能差异和选择依据
with() 生成的 SQL 更可控,适合确定性场景;load() 更灵活,适合运行时决策,但多一次查询往返。两者都不解决「关联数据量过大」问题——如果 posts 有几百条,预加载后内存占用仍可能飙升。
此时要考虑:
- 用
select()限制字段:User::with(['posts' => fn($q) => $q->select('id', 'user_id', 'title')]) - 分页关联数据:
$user->posts()->paginate(10)(不能用with()实现) - 真正大数据场景,放弃 Eloquent 关联,改用原生查询或 DTO 手动组装
预加载不是银弹,关键在理解数据流起点:是从 DB 查还是从内存/缓存来——选错方法,优化就从第一行代码开始失效。










