Laravel 5.7 Eloquent如何避免加载父模型时自动加载关联$with模型?
$with属性导致的过度关联加载问题 这确实是Laravel开发中使用$with属性时很容易踩的坑——自动嵌套加载会让关联查询像滚雪球一样膨胀,尤其是模型层级深、数据量不小的时候,很容易拖慢页面加载速度。我给你几个实用的解决方案,你可以根据自己的业务场景选择:
1. 临时禁用自动加载的关联(最直接的临时方案)
如果你只是在某个特定场景下不需要加载$with里的关联,可以用without()方法精准控制要排除的关联,甚至是嵌套的关联:
- 排除ModelA自身的所有自动关联:
// 完全不加载ModelA的$with里的任何关联 $modelAs = ModelA::withoutEagerLoads()->get(); // 只排除指定的几个关联,比如ModelK和ModelM $modelAs = ModelA::without(['modelK', 'modelM'])->get(); - 排除嵌套的关联(比如ModelK里自动加载的ModelM):
$modelAs = ModelA::without('modelK.modelM')->get();
2. 重构$with属性为按需手动预加载(长期优化方案)
如果$with里的关联并不是所有业务场景都必需,那最好直接移除模型里的$with属性,改成在需要加载关联的地方手动用with()指定:
// 先移除ModelA里的$with属性 // 在需要加载关联的业务逻辑中手动指定: $modelAs = ModelA::with(['modelK', 'modelL'])->get();
这样完全由你控制加载哪些关联,不会触发意外的嵌套自动加载,从根源上避免查询爆炸。
3. 延迟加载指定关联(适合已获取模型实例的场景)
如果已经拿到了ModelA的实例,现在只需要加载某个特定关联,而不是全部自动加载的内容,可以用loadMissing()方法,它只会加载尚未加载的指定关联:
// 假设$modelA是已经从数据库获取的ModelA实例 $modelA->loadMissing(['modelK']);
这样不会触发ModelA的$with里的其他关联,也不会触发ModelK自身的$with关联(除非你特意指定)。
4. 优化预加载逻辑,过滤不必要的嵌套关联
如果确实需要加载部分关联,但不想加载子模型的自动关联,可以在with()里用闭包来禁用子模型的$with:
$modelAs = ModelA::with([ 'modelK' => function ($query) { // 禁用ModelK自身的自动加载关联 $query->withoutEagerLoads(); // 或者只排除ModelK里的特定关联 $query->without(['modelM', 'modelN']); }, 'modelM' => function ($query) { $query->without(['modelX', 'modelY']); } ])->get();
这样既能加载需要的关联,又能避免不必要的嵌套查询。
5. 用全局作用域替代$with(更灵活的全局加载方案)
如果大部分场景需要加载关联,但偶尔不需要,可以把自动加载逻辑改成全局作用域,然后在不需要的场景中移除:
// 在ModelA模型中定义全局作用域 protected static function booted() { static::addGlobalScope('default-relations', function ($query) { $query->with(['modelK', 'modelL', 'modelM']); }); }
然后在不需要加载这些关联的场景中:
$modelAs = ModelA::withoutGlobalScope('default-relations')->get();
这种方式比$with更灵活,能随时开关全局关联加载。
额外注意:检查循环关联
你提到ModelA、ModelK都加载了ModelM,这种情况可能会导致重复查询。可以在预加载时合并重复的关联,或者调整模型的$with逻辑,避免不必要的重复加载。
内容的提问来源于stack exchange,提问作者Muzaffar Shaikh

