[大阪/横滨/德岛] 寻找基础设施/服务器端工程师!

[大阪/横滨/德岛] 寻找基础设施/服务器端工程师!

【超过500家企业部署】AWS搭建、运维、监控服务

【超过500家企业部署】AWS搭建、运维、监控服务

【CentOS的后继者】AlmaLinux OS服务器搭建/迁移服务

【CentOS的后继者】AlmaLinux OS服务器搭建/迁移服务

[仅适用于 WordPress] 云服务器“Web Speed”

[仅适用于 WordPress] 云服务器“Web Speed”

[便宜]网站安全自动诊断“快速扫描仪”

[便宜]网站安全自动诊断“快速扫描仪”

[预约系统开发] EDISONE定制开发服务

[预约系统开发] EDISONE定制开发服务

[注册100个URL 0日元] 网站监控服务“Appmill”

[注册100个URL 0日元] 网站监控服务“Appmill”

【兼容200多个国家】全球eSIM“超越SIM”

【兼容200多个国家】全球eSIM“超越SIM”

[如果您在中国旅行、出差或驻扎]中国SIM服务“Choco SIM”

[如果您在中国旅行、出差或驻扎]中国SIM服务“Choco SIM”

【全球专属服务】Beyond北美及中国MSP

【全球专属服务】Beyond北美及中国MSP

[YouTube]超越官方频道“美由丸频道”

[YouTube]超越官方频道“美由丸频道”

[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 作为条件/约束/搜索。
顺便说一句,如果您有可能经常使用的条件,可以方便地在模型文件中创建方法以提高可读性和便利性。

结束

我希望这篇文章将使访问世界数据库变得更加容易。

如果您觉得这篇文章有帮助,请点赞!
6
加载中...
6 票,平均:1.00 / 16
332
X Facebook 哈特纳书签 口袋
[2025.6.30 Amazon Linux 2 支持结束] Amazon Linux 服务器迁移解决方案

[2025.6.30 Amazon Linux 2 支持结束] Amazon Linux 服务器迁移解决方案

[大阪/横滨] 积极招募基础设施工程师和服务器端工程师!

[大阪/横滨] 积极招募基础设施工程师和服务器端工程师!

写这篇文章的人

关于作者

金针木

我玩各种游戏,包括 FPS、RPG、MMO、制作等等。