认识 N+1 问题

背景

L02课程的5.5节介绍了显示话题列表。需要做以下两步:

1. 显示话题列表。

2. 把每个话题的分类显示出来,还要显示这个话题的作者。
如图:

认识 N+1 问题

E-R图如下:

Laravel

实现的代码片段:

// 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中提示:

Laravel


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;

结果如下:

Laravel

但问题是使用原生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'));

    }

    ...

}

结果如下:

Laravel

查询的SQL语句由原来的60多条变为了现在的4条,没有重复执行的语句。

参考:

1. 什么是 N+1 问题,以及如何解决 Laravel 的 N+1 问题?

2. 文档

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

N + 1 问题只存在于模型关联中,通常需要在 循环输出关联数据 时要特别注意。

「在没有加载模型关系时,直接访问模型关系,就会造成一条额外查询」

2年前 评论
Moonshadow2333 (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!