[EagerLoad] leftJoin 和 with 哪个更有用?[N+1 问题]

我是系统开发部的榎木,最近我的前辈和上司都跟我说
“任何没写过原始查询语句的人都应该认真对待这件事”。 (顺便说一句,我现在负责新毕业生的MySQL培训,所以我对此非常担忧。)
N+1 问题
(暂且不谈我的故事)假设
建立了以下关系。
class Bolg { public function comments(): HasMany { return $this->hasMany(Comment::class); } }
获取博尔格模型
$blogs = Bolg::query()->first();
此处发出以下查询:
"select * from `blogs`"
通过继续循环,我们可以得到与每个 Blog 模型关联的所有 Comment 模型。
$comments = $blogs->map(function ($blog) { return $blogs->comments->first(); });
当然,由于这是一个循环,以下查询将执行与章节数相同的次数。
"select * from `blogs` where `blogs`.`blog_id` = ? and `blogs`.`blog_id` is not null"
初始查询(用于检索所有记录)会在发出 N 个查询后执行一次。
这就是 1+N 或 N+1 问题。
问题在于,自然而然地,随着章节数的增加,查询次数也会增加,这无疑是个问题。
加入并避免 N+1
现在我们已经讲到重点了。
如上所述,每次都发出查询效率极低,因此应该使用 (left)join 或 with 语句。
(顺便一提,在某些情况下,每次都进行检索可能更好,所以要谨慎选择。)
这些方法都可以用来避免发送大量查询。
但是,它们各自的作用有所不同。
什么是连接?
该连接是内连接,它只
保留表中共有的数据,并剔除其余数据。
leftJoin 执行左外连接
,返回左表中的所有行以及右表中所有匹配的行。这样,
您就可以获得符合连接条件的数据,同时保留左表中的所有数据。
这些连接是 SQL 的一个特性。
它们可以在一个查询中连接多个表。
就查询而言,它们看起来像这样:
0 => array:3[ "query" => "select * from `blogs` inner join `comments` on `blogs`.`id` = `blog_id`" "bindings" => [] "time" => 1.5 ],
仅仅增加了一个查询!
这真是巨大的进步!
那么,和……在一起呢?
和
with 查询
运行两次,这比 join 更糟糕,但比 N+1 循环有了巨大的改进。
顺便一提,`with` 函数的参数字符串是模型文件中关系定义方法的名称。
(它本质上与相关模型的名称相同,但如果您没有将其定义为方法,则无法使用 `with` 函数,因此请务必小心。)
$blogAndComments= Blog::query() ->with('comments') ->first();
查看查询语句,我们可以看到使用 whereIn 可以一次性检索所有数据。
0 => array:3[ "query" => "select * from `blogs`" "bindings" => [] "time" => 0.5 ], 1 => array:3[ "query" => "select * from `blogs` where `blogs`.`blogt_id` in (?)" "bindings" => [] "time" => 1.5 ],
各自的特点和用途
我们采用两种方法获得了该模型,现在让我们来看看每种方法的特点。
●(左)JOIN
函数根据连接条件合并模型。JOIN
是一个 SQL 函数,用于一次性从多个表中检索数据。
检索时,只会创建符合连接条件的数据量。
在本例中,您可以将最终创建的数据量视为与 Comment 模型中的数据量相同。
如果连接多个表,则可能会生成无效数据。
然而,能够一次性获取所有数据是很有吸引力的,
当不需要将其作为模型或需要简单、扁平的数据时,它是理想的选择。
● 它以
关联数组的形式检索模型,而无需将它们合并。
预先加载ORM 中的,它解决了 N+1 问题。
在本例中,与 Bolg 模型关联的 Comment 模型将存在于“relations”属性下。
模型可以嵌套,并按照其父子关系的层级顺序检索,因此模型不会合并。
相关的模型和数据会一次性全部检索,从而消除冗余数据。
当您希望将检索到的数据直接作为模型处理时,此功能也很有用。
顺便说一下,可以使用 with 来检索关系,只要它们持续存在,例如 (ModelA.ModelB.ModelC.),但由于嵌套意味着所有传递的模型都将作为外壳包含在内,因此最终需要使用 unset 或类似方法将其删除。
总结与题外话
现在所有信息都已公布,我将对其进行总结,然后在结论之前再补充一些题外话。
概括
你觉得怎么样?
信息量很大,所以我把它整理成了一个表格。

我不知道哪个才真正有用!
虽然结果大致都是从多个表中检索数据,但它们的具体操作却截然不同。
因此,总的结论是:“根据所需的数据格式更改采集方法。”
最简单的判断方法是确定
您需要的是原始模型还是可以接受合并模型或者,您可以根据哪种形式更便于后续处理,选择嵌套形式或平面形式
此外,我认为应该
根据要检索的数据总量来调整因为对于加载关系来说,嵌套越深,速度就越慢,相比之下,连接操作速度更慢。
换句话说,这取决于具体情况。
附注(EagerLoad)
我想写一下关于 EagerLoad 的内容。
你可能已经明白,它的基本含义是不希望每次都执行 SQL 语句。
这有点枯燥,所以让我详细解释一下。
“EagerLoad 仅适用于动态属性”这就是它的含义。
(也许这说不通)
首先,我们来谈谈动态属性和关系方法之间的区别。
换句话说,这和
$blog= 新建博客;$blog->评论;
这就是区别所在。
$blog = new Blog; $blog->comments();
->comments 是一个动态属性,它返回一个集合,并且
具有“延迟加载”属性,这意味着相关数据仅在访问时加载,
而 ->comments() 返回一个相关对象。
你不能将带有动态属性的查询构建器链接在一起,但
你可以像这样链接它们: ->comments()->where('create_user_id', 1)
因此,最好将其作为关联对象检索。
这里,请记住 EagerLoad 的属性,例如 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 与条件、约束和搜索结合使用。
另外,如果您有一些经常使用的条件,可以将它们转换为模型文件中的方法,这样可以提高代码的可读性,也更方便使用。
结束
我希望这篇文章能让访问世界各地的数据库变得更容易一些。
6