【Laravel】EagerロードでN+1問題を解決する

2 min read読了の目安(約2600字

はじめに

LaravelではでN+1問題を解決するためにEagerロードが準備されています。
今回、公式ドキュメントを参考に、Eagerロードの使用の有無でクエリの実行件数とかかった時間を比較してみました。
なお、books, authorsテーブルにはそれぞれ100件のテストデータを入れています。

準備

Bookモデルにリレーションを追記します。

app/Models/Book.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    use HasFactory;

    public function author()
    {
        return $this->belongsTo(Author::class); // 追記
    }
}

検証

Eagerロードを使わない場合と使った場合の2パターンを比較します。
クエリの内容、実行時間の確認にはenableQueryLogを使用しました。

Eagerロードを使わない場合

まずは全ての本を取得して、その後本の著者を取得するために各本に対して別のクエリを実行してみます。

app/Http/Controllers/BookController.php
<?php

namespace App\Http\Controllers;

use App\Models\Book;

class BookController extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @return \Illuminate\Http\Response
     */
    public function __invoke()
    {
        \DB::enableQueryLog();

        $books = Book::all();

        foreach ($books as $book) {
            echo $book->author->name;
        }

        dd(\DB::getQueryLog());
    }
}

実行したところ、計101件のクエリが実行されていました。
実行結果(Eagerロードなし)
1回は本のデータ取得、そのほか100件は各本の著者を取得するクエリです。
実行時間はまちまちですが、約108ミリ秒でした。

# 一回目
select * from `books`

# 二回目以降
select * from `authors` where `authors`.`id` = 1 limit 1
select * from `authors` where `authors`.`id` = 2 limit 1
select * from `authors` where `authors`.`id` = 3 limit 1
・
・
・

Eagerロードを使った場合

次にEagerロードを使用してみます。

app/Http/Controllers/BookController.php
<?php

namespace App\Http\Controllers;

use App\Models\Book;

class BookController extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @return \Illuminate\Http\Response
     */
    public function __invoke()
    {
        \DB::enableQueryLog();

        $books = Book::with('author')->get(); // 変更

        foreach ($books as $book) {
            echo $book->author->name;
        }

        dd(\DB::getQueryLog());
    }
}

実行したところ、計2件のクエリが実行されていました。
実行結果(Eagerロードあり)
1回は本のデータ取得、もう1回は全ての本の全ての著者を取得するクエリです。
where inを使うことで一括で著者を取得していることが分かります。
ちなみに著者を取得するクエリはforeachの中ではなく、Book::with('author')->get()の時点で実行されています。
実行時間は約9ミリ秒でEagerロードを使用しない場合(108ミリ秒)と比較して実行速度が上がっておりパフォーマンスが向上しています。

# 一回目
select * from `books`

# 二回目
select * from `authors` where `authors`.`id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ... 100)