Laravel中load()不是懒加载,而是对已存在模型实例的同步预加载;真正懒加载指访问$user->posts自动查库,Laravel 8.37+开发环境默认禁用,需用Model::preventLazyLoading()控制。

别被“Lazy Eager Loading”这个混搭词迷惑——Laravel 里没有这种东西。Eloquent 的 load() 不是懒加载,也不是延迟执行,它就是一次同步、显式的预加载;而真正的懒加载(访问 $user->posts 自动查库)默认在 Laravel 8.37+ 开发环境已被禁用,且必须主动关掉才能用于生产。
load() 是什么?不是懒加载,是“事后补查”
load() 的作用很明确:对**已经存在的模型实例或集合**,再发起一次关联查询,把数据塞进内存。它不延迟到属性访问时才触发,调用那一刻 SQL 就发出去了。
- 常见错误现象:
$user = User::find(1); $user->load('posts');看似省事,但若之前已查过posts(比如通过with()),这次就是冗余查询 - 正确使用场景:API 根据 query 参数决定是否返回评论,主模型已从缓存/分页中取出,再按需补查
comments - 不能链在查询构建器上:
User::query()->load('posts')会报错,load()只接受模型实例或集合 - 嵌套支持但非“逐个懒查”:
$users->load('posts.comments')仍是一次性批量查完所有关联,不是每个$user单独触发
loadMissing() 才是你该默认用的替代方案
手动检查 relationLoaded() 再 load(),既啰嗦又容易漏判。而 loadMissing() 会自动跳过已加载的关联,只补缺。
- 典型用法:放在访问器里,比如
getPostsCountAttribute()中写$this->loadMissing('posts');,后续$this->posts->count()就不会重复查 - 模板前统一补全:
$users->loadMissing('profile', 'settings');,比散落各处的load()更安全 - 与缓存配合时更自然:先
Cache::get(...),没命中再loadMissing()回源,不用自己维护加载状态 - 注意:它不支持闭包条件过滤,要带 where 就得回退到
load()
with() 和 load() 到底怎么选?看查询阶段是否已结束
核心判断只有一条:主模型数据是否已经从数据库(或缓存)里取出来了?
- 没取出来 → 用
with():如User::with('posts')->findOrFail($id),这是最优路径,SQL 合并、无冗余 - 已经取出来了 → 用
load()或loadMissing():如$users = cache()->get('top_users'); $users->loadMissing('avatar'); - 误用典型:
User::findOrFail($id)->with('posts')完全无效,with()对已执行的查询无意义 - 性能影响:对集合调用
load()时,Eloquent 会自动去重外键,避免 N+1;但若手动foreach+load(),就真变 N 次查询了
真正要关的不是 load(),而是隐式懒加载
很多人以为关掉 load() 就能防 N+1,其实不是。真正危险的是未加防护的 $user->posts 访问——这才是 Laravel 默认开启、且极易在循环中爆炸的懒加载行为。
- 开发环境强制关:在
AppServiceProvider::boot()加Model::preventLazyLoading(!app()->isProduction());,未预加载就访问关联直接抛异常 - 别信
$shouldUseLazyLoading = false:这是过时写法,Laravel 9+ 已弃用,新版只认preventLazyLoading() - 关掉之后,
load()和loadMissing()依然可用,它们是“可控的显式加载”,不是被禁的对象 - 最容易被忽略的一点:即使你用了
loadMissing(),如果模型关系定义本身有缺陷(比如中间表字段名错、foreignKey漏写),照样查不到数据,且不会报错——得靠调试日志或 DB 查询确认


















