Laravel5.5+中通过即时加载关系/预加载嵌套关系避免n+1查询问题测试demo

2023-06-01 00:00:00 关系 加载 嵌套

在数据库查询优化中,这条建议你可能听说过无数次了。

测试demo中,所以我会尽可能简短。

让我们假设您有以下场景:

控制器:

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all();
        return view('posts.index', ['posts' => $posts ]);
    }
}

视图:

// posts/index.blade.php 文件

 @foreach($posts as $post)
    <li>
        <h3>{{ $post->title }}</h3>
        <p>Author: {{ $post->author->name }}</p>
    </li>
@endforeach

上面的代码是检索所有的帖子,并在网页上显示帖子标题和作者,假设帖子模型关联作者。

执行以上代码将导致运行以下查询。

select * from posts // 假设返回5条数据
select * from authors where id = { post1.author_id }
select * from authors where id = { post2.author_id }
select * from authors where id = { post3.author_id }
select * from authors where id = { post4.author_id }
select * from authors where id = { post5.author_id }

如上,

1 条查询来检索帖子,5 条查询来检索帖子的作者(假设有 5 篇帖子)。

因此对于每篇帖子,都会进行一个单独的查询来检索它的作者。


所以如果有 N 篇帖子,将会产生 N+1 条查询(1 条查询检索帖子,N 条查询检索每篇帖子的作者)。

这常被称作 N+1 查询问题。

避免这个问题,可以像下面这样预加载帖子的作者。


预加载关系:

$posts = Post::all(); // Avoid doing this
$posts = Post::with(['author'])->get(); // Do this instead



预加载嵌套关系(基于上面的例子基础上)

从上面的例子,考虑作者归属于一个组,同时需要显示组的名字的情况。因此在 blade 文件中,可以按下面这样做。

@foreach($posts as $post)
    <li>
        <h3>{{ $post->title }}</h3>
        <p>Author: {{ $post->author->name }}</p>
        <p>Author's Team: {{ $post->author->team->name }}</p>
    </li>
@endforeach

接着

$posts = Post::with(['author'])->get();

得到下面的查询:

select * from posts // Assume this query returned 5 posts
select * from authors where id in( { post1.author_id }, { post2.author_id }, { post3.author_id }, { post4.author_id }, { post5.author_id } )
select * from teams where id = { author1.team_id }
select * from teams where id = { author2.team_id }
select * from teams where id = { author3.team_id }
select * from teams where id = { author4.team_id }
select * from teams where id = { author5.team_id }

如上,尽管预加载了 authors  关系,仍然产生了大量的查询。

这是因为没有预加载 authors 上的 team 关系。

通过下面这样来解决这个它。


预加载嵌套关系:

$posts = Post::with(['author.team'])->get();


执行得到下面的查询。

select * from posts // Assume this query returned 5 posts
select * from authors where id in( { post1.author_id }, { post2.author_id }, { post3.author_id }, { post4.author_id }, { post5.author_id } )
select * from teams where id in( { author1.team_id }, { author2.team_id }, { author3.team_id }, { author4.team_id }, { author5.team_id } )

通过预加载嵌套关系,可以将查询数从 11 减到 3。


以上就是通过即时加载关系/预加载嵌套关系避免n+1查询问题

相关文章