认识 N+1 问题
背景
L02课程的5.5节介绍了显示话题列表。需要做以下两步:
1. 显示话题列表。
2. 把每个话题的分类显示出来,还要显示这个话题的作者。
如图:
E-R图如下:
实现的代码片段:
// Topic 模型和其他两个模型的关系
class Topic extends Model
{
...
public function category(){
return $this->belongsTo(Category::class);
}
public function user(){
return $this->belongsTo(User::class);
}
...
}
// 在TopicsController定义index方法获取30条帖子:
class TopicsController extends Controller
{
public function index()
{
$topics = Topic::paginate(30);
return view('topics.index', compact('topics'));
}
}
// 显示话题列表,每条话题要显示作者和所属的类别。
@foreach ($topics as $topic)
...
{{ $topic->user->name }}
...
{{ $topic->category->name }}
...
@endforeach
出现的问题:
在debugbar
中提示:
62 statements were executed, 60 of which were duplicated, 2 unique
总共执行了 62 条语句,其中 60 条是重复的。
先用原生php复现一下N+1问题。
// 1. 找出10条帖子。
select `id`, `title`, `user_id`, `category_id` from topics limit 10;
// 2. 遍历结果集
foreach($result as $row){
// 利用user_id找出这条帖子的发布者
select `name` from users where id = $row['user_id'];
// 利用category_id找出这条帖子的所属的类别
select `name` from categories where id = $row['category_id'];
}
分析:
1. 一次性取出10条帖子使用了一次查询。
2. 遍历10条帖子时,需要把每条帖子的作者找出来,循环了10次,就查询了10次。
3. 遍历10条帖子时,需要把每条帖子所属的类别找出了,循环了10次,就查询了10次。
4. 总共查询的次数:1+10+10=21次。
什么是N+1问题?
在基本级别,ORM 是 “懒惰” 加载相关的模型数据。但是,ORM 应该如何知道你的意图?在查询模型后,您可能永远不会真正使用相关模型的数据。不优化查询被称为 “N + 1” 问题。
这是什么是 N+1 问题,以及如何解决 Laravel 的 N+1 问题?中的定义。
一句话总结:不优化查询被称为 “N + 1” 问题。
如何解决N+1问题?
1. 使用原生SQL语句:
优化也很简单,利用内联接就能把需要的数据一次性取出来。SQL代码如下:
select
t.id,
t.title,
u.name as user,
c.name as category
from topics as t
join users as u
join categories as c
on t.user_id=u.id and t.category_id=c.id
order by t.id
limit 10;
结果如下:
但问题是使用原生SQL语句很容易出现SQL注入的问题,所以暂不考虑。
2. 使用预加载:
什么是预加载:
在查询父模型时主动加载关联关系。
使用预加载:
在构建查询时,可以使用 with 方法指定应该预加载哪些关系:
在本例中,我们需要预加载 user 和 category 关联关系。
class TopicsController extends Controller
{
...
public function index()
{
$topics = Topic::with('user', 'category')->paginate(30);
return view('topics.index', compact('topics'));
}
...
}
结果如下:
查询的SQL语句由原来的60多条变为了现在的4条,没有重复执行的语句。
参考:
1. 什么是 N+1 问题,以及如何解决 Laravel 的 N+1 问题?
2. 文档
本作品采用《CC 协议》,转载必须注明作者和本文链接
N + 1 问题只存在于模型关联中,通常需要在 循环输出关联数据 时要特别注意。
「在没有加载模型关系时,直接访问模型关系,就会造成一条额外查询」