[EagerLoad] leftJoin 和 with 哪个更有用?[N+1 问题]
“如果你以前从未编写过原始查询,那么你最好有真正的危机感,”
系统开发部成员 Enoki 说,他的前辈和上级最近经常这样告诉他。
(顺便说一句,我现在负责MySQL应届毕业生的培训,所以我有一种真正的危机感。)
N+1问题
(先不说我的故事)
我们假设建立了这样的关系。
class Bolg { public function comments(): HasMany { return $this->hasMany(Comment::class) } }
获取博尔格模型
$blogs= Bolg::query()->first();
这里发出以下查询
“从`博客`中选择*”
如果进一步循环,您可以获得与每个 Blog 模型关联的所有 Comment 模型。
$comments= $blogs->map(function ($blog) { return $blogs->comments->first(); });
当然,由于它是在循环中,因此将针对章节数执行以下查询。
“从`blogs`中选择*,其中`blogs`.`blog_id`=?并且`blogs`.`blog_id`不为空”
发出一个获取所有第一个结果的查询 + N 个查询。
这是一个1+N的问题。
当然,问题在于,发出的查询数量与章节数量一样多,因此问题只是负载增加了。
加入并避免 N+1
我们来到了正题。
如上所述,每次发出查询效率极低,因此使用 (left)join 或 with。
(顺便说一下,在某些情况下,每次都检索可能会更好,因此请谨慎选择。)
其中每一个都可以用来防止大量查询流动。
然而,他们各自做不同的事情。
什么是加入?
join 执行内连接。
获取表之间的公共数据并消除其余数据。
leftJoin 执行左外连接。
返回左表中的所有行以及右表中的任何匹配行。
检索符合连接条件的数据,同时保留左表中的所有数据。
这些连接是 SQL 的一项功能。
您正在查询中连接多个表。
查询看起来像这样。
0 => array:3[ "query" => "从 `blogs` 上的 `blogs` 内连接 `comments` 中选择 *。`id` = `blog_id`" "绑定" => [] "时间" => 1.5 ],
只需要一次查询。
感觉这是巨大的进步。
那么与呢?
和
with 查询执行两次。
虽然失去了 join,但考虑到循环运行了 N+1 次,这已经是一个巨大的进步了。
顺便说一句,用作 with 参数的字符串是模型文件中关系定义方法的名称。
(它实际上与关系目标的模型名称相同,但请注意,除非将其定义为方法,否则不能使用 with 。)
$blogAndComments= 博客::query() ->with('comments') ->first();
查看查询,您可以看到由于 whereIn 可以一次性检索到所有数据。
0 => 数组:3[ "查询" => "从`博客`中选择*" "绑定" => [] "时间" => 0.5 ], 1 => 数组:3[ "查询" => "选择 *来自 `blogs`,其中 `blogs`.`blogt_id` in (?)" "bindings" => [] "time" => 1.5 ],
各自的特点和用途
我们使用两种方法获取模型,但让我们看一下每种方法的特点。
●(左)
Join 根据连接条件组合模型。
Join 是一项 SQL 功能,用于同时从多个表中检索数据。
检索时,仅创建符合连接条件的数据量。
例如,您可以认为最终数据的创建次数与评论模型的数量一样多。
如果这连接多个表,可能会创建不正确的数据。
当您的模型不需要它或
,它是理想的选择
●
以关联数组的形式获取with 模型,而不进行组合。
在 ORM 中预加载模型关系来解决 N+1 问题
例如,与 Bolg 模型关联的 Comment 模型将存在于名为“关系”的属性中。
模型可以按照父子关系的层次顺序嵌套和检索,因此模型不会组合。
立即检索相关模型和相关数据,而不会创建冗余数据。
当您想要将获取的数据按原样视为模型时,它也很有用。
顺便说一句,with只要关系持续就可以得到,比如(ModelA.ModelB.ModelC.),但由于是嵌套的,所有传递过来的模型都会被附加为外壳,所以最后,unset等等。你需要去做并消除它。
总结和旁白
现在我已经掌握了所有的信息,我将总结一下并用一点题外话来结束。
概括
你怎么认为?
由于信息量很大,所以我决定将其放入表格中。
不知道哪些是真正有用的! ! !
粗略地说,结果是从多个表中检索数据,但它们所做的事情却截然不同。
所以,这是一个典型的总结:“请根据您想要的数据类型更改获取方法”。
我认为最简单的判断方法就是看
是否与模型形状相同,或者是否可以将它们组合起来或者,哪种形状在将来的处理中更容易使用(嵌套形状或扁平形状),您可以选择使用哪一种。
另外,我认为应该
根据你想要获取的数据总量来改变由于 with 读取关系,嵌套越深,它比 join 越慢。
换句话说,这取决于情况。
急切加载
我想写一篇关于 EagerLoad 的文章。
您现在可能已经明白,底线是您不想每次都发出 SQL。
仅此一项就很无聊,所以我将详细介绍一下。
“除非是动态属性,否则无法使用 EagerLoad”这就是它的意思。
(我想这可能没有意义)
如果你了解Fate的Fragarak,就很容易想象了。
首先,我们来谈谈动态属性和关系方法之间的区别。
换句话说,这和
$blog=新博客;$blog->评论;
这就是区别。
$blog=新博客;$blog->comments();
->comments 成为一个动态属性并返回一个 Collection。
此外,它还具有“延迟加载”的属性,仅在访问关系数据时才加载关系数据。
另一方面,在 ->comments() 的情况下,它成为一个关系对象。
对于动态属性,您无法连接查询构建器,但
对于关系对象
您可以像 ->comments()->where('create_user_id', 1) 那样连接它们。
在这种情况下,将其作为关系对象获取似乎更好。
记住 EagerLoads 的属性,包括 with。
是的,您是提前发出询问。
由于正在检索数据(在关系属性中),因此
当您写入 $blog->comment->create_user_id 时,查询将不会运行。
这是“除非是动态属性,否则无法使用 EagerLoad”
换句话说,“如果要使用EagerLoad,后续的处理也必须做成动态属性,否则就没有意义了!!”这就是它的意思。
矛盾的是,事情就是这样发生的。
这正是“先不后人”的意思。
那么,检索时如何应用条件、约束和搜索! !对于那些说的人。
$blog= Bolg::with(['comments' => function ($query) { $query->where('create_user_id', 1); }])->first(); $comments= $blog->comments ;
通过这样做,您可以使用 EagerLoad 作为条件/约束/搜索。
顺便说一句,如果您有可能经常使用的条件,可以方便地在模型文件中创建方法以提高可读性和便利性。
结束
我希望这篇文章将使访问世界数据库变得更加容易。