在基本级别,ORM 是 “懒惰” 加载相关的模型数据。在查询模型后,您可能永远不会真正使用相关模型的数据。不优化查询被称为 “N + 1” 问题。当您使用对象来表示查询时,您可能在不知情的情况下进行查询。
一、N+1问题
介绍
在基本级别,ORM 是 “懒惰” 加载相关的模型数据。但是,ORM 应该如何知道你的意图?在查询模型后,您可能永远不会真正使用相关模型的数据。不优化查询被称为 “N + 1” 问题。当您使用对象来表示查询时,您可能在不知情的情况下进行查询。
想象一下,您收到了100个来自数据库的对象,并且每条记录都有1个关联的模型(即belongsTo)。使用ORM默认会产生101条查询; 对原始100条记录 进行一次查询,如果访问了模型对象上的相关数据,则对每条记录进行附加查询。在伪代码中,假设您要列出所有已发布帖子的发布作者。从一组帖子(每个帖子有一位作者),您可以得到一个作者姓名列表,如下所示:
$posts = Post::published()->get(); // 一次查询
$authors = array_map(function($post) {
// 生成对作者模型的查询
return $post->author->name;
}, $posts);
我们并没有告诉模型我们需要所有作者,因此每次从各个Post 模型实例中获取作者姓名时都会发生单独的查询 。
解决
预加载功能
使用with()方法指定想要预加载的关联:
$users = User::where(“age”, “>”, 18)
->with(“hasBalance”)
->select();
hasBalance 是什么呢?
它是在User模型中定义的一个方法:
class User extends Model
{
// …
// User模型与Balance 模型进行一对一关联
public function hasBalance()
{
return $this->hasOne(Balance::class, “user_id”, “user_id”);
}
}
通过这个方法让User 模型与Balance 模型进行一对一关联。
延伸阅读:
二、N+1 问题由来
假设需要现在需要查找模型 A 的 n 条数据,而模型 A 又关联了另外多个的模型,这里假设 A 关联了模型 B 和 C. 现在需要查找 n 条 A 的记录(这里需要 1 条 SQL 语句,例如 select * from A ),同时把这 n 条记录关联的 B, C 记录也查出来(这里需要 n 条查询 B 的语句和 n 条查询 C 的语句),于是最后需要的 SQL 语句数目为 2n + 1。
将 2n 抽象出来,假设需要查找的是 n 条 A 的记录(count(SQL) = 1),同时需要把与 A 关联的 k 个模型的记录也查找处理出来( count(SQL) = n * k ), 设 n * k = N, 最后查询的 SQL 语句数目,即 N + 1。