N+1問題について理解する

:::note
### 概要
N + 1問題とは
元のモデルを取得するクエリ1個に加えて、関連するモデルのデータの取得をレコードの数（N個）分発行されることによって起こるクエリが最適化されていない問題のことをいいます。
これによりアプリケーションのパフォーマンスが低下します。

Laravel では 例えば`with`でEagerLoading によってこの N + 1 問題を回避できます。
:::

# 背景
業務でLaravelを使っていて、Modelのリレーションを使ってwith()で記述しました。
これが「EagerloadingでN+1問題を回避できる」という解説は見たのですが、そもそもN+1問題について分からないので勉強しようと思います。

:::note
Laravelで検証します
:::

こちらで動作確認できます。

https://github.com/yamatai12/n-plus-1-query-problem-sample

# N+1問題とは

https://laravel.com/docs/11.x/eloquent-relationships#eager-loading

N+1クエリ問題と言うそうです。

`Book`モデルと`Author`モデルで考えます。

# テーブル作成

books（本テーブル）
本の情報を保持します。

| Column        | Type          | Description                    |
|---------------|---------------|--------------------------------|
| id   | INT           | ID（主キー）             |
| title          | VARCHAR(50)   | タイトル                           |
| author_id | INT           | 著者のID（外部キー）             |

---

authors（著者テーブル）
著者の情報を保持します。

| Column          | Type          | Description                    |
|-----------------|---------------|--------------------------------|
| id    | INT           | ID（主キー）               |
| name  | VARCHAR(50)   | 名前                         |

# サンプルデータ挿入

books（本テーブル）

| id  | title            | author_id | created_at | updated_at |
|-----|------------------|-----------|------------|------------|
|  1  | John's Book      |     1     |    NULL    |    NULL    |
|  2  | Paul's Book      |     2     |    NULL    |    NULL    |
|  3  | George's Book1st |     3     |    NULL    |    NULL    |
|  4  | George's Book2nd |     3     |    NULL    |    NULL    |

authors（著者テーブル）

| id  | name   | created_at | updated_at |
|-----|--------|------------|------------|
|  1  | John   |    NULL    |    NULL    |
|  2  | Paul   |    NULL    |    NULL    |
|  3  | George |    NULL    |    NULL    |
|  4  | Ringo  |    NULL    |    NULL    |

# Model作成

booksテーブルとauthorテーブルは多対一の関係にあります。
一人の著書によって色々な本が書かれます。

これをModelで記載すると次のようになります。

```php:Bookモデル
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}

```

# N + 1 問題となるクエリを発行する

```php:app/routes/web.php
<?php

use App\Models\Book;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;

Route::get('/', function () {

    DB::enableQueryLog();
    $books = Book::all();

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

    // 実行されたクエリを表示
    dd(DB::getQueryLog());

    return view('welcome');
});

```

以下のクエリが実行されていることがわかりました。

```sql
select * from `books`;
select * from `authors` where `authors`.`id` = ? limit 1;
select * from `authors` where `authors`.`id` = ? limit 1;
select * from `authors` where `authors`.`id` = ? limit 1;
select * from `authors` where `authors`.`id` = ? limit 1;
```

`id`には各レコードのauthor_idが代入されます。

booksのレコードを全件取得するクエリ（1件）に加えて、レコード数分のクエリ（4件）が発行されています。
1 + 4　

booksのレコード数が N 件に増えれば
1 + N　とも捉えられます。

これを N + 1 問題と言うそうです。

# Eager Loading（イーガーローディング）

Laravelにおけるデータベースクエリの最適化手法の1つです。

イーガーローディングを使用することで、先ほどの N + 1 個のクエリを2個のクエリに減らすことができます。

クエリを作成する際に、どの関連するモデルのデータをロードするかを`with`メソッドで指定することができます。

`eager` は`すぐに`という意味らしいです。

```php:app/routes/web.php
<?php

use App\Models\Book;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;

Route::get('/', function () {

    DB::enableQueryLog();
    $books = Book::with('author')->get();
 
    foreach ($books as $book) {
        echo $book->author->name;
    }

    // 実行されたクエリを表示
    dd(DB::getQueryLog());

    return view('welcome');
});

```

以下のクエリが実行されていることがわかりました。

```sql
select * from `books`;

select * from `authors` where `authors`.`id` in (1, 2, 3);
```

1つはすべての書籍を検索するクエリ、もう1つはすべての書籍のすべての著者を検索するクエリが発行されていることがわかります。

実際は今回みたいに単純なクエリばかりではないですが、これでクエリを最適化できていると言えます。

# 最後に
- バックエンドの知識が伸び代しかないので今後も業務から遅延学習していきます
- N + 1 問題の概要がつかめました
- ORMの記述だけでイメージするのではなく、実際に発行されているクエリを見ることで本当に N + 1 問題が起こっていることを確認できたので理解が深まりました
- Lazy Eager Loading についても勉強したいです

# 参考

https://laravel.com/docs/11.x/eloquent-relationships#eager-loading

https://magical-programming.quora.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%92%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%A8%E5%87%BA%E3%81%A6%E3%81%8F%E3%82%8B%E8%8B%B1%E5%8D%98%E8%AA%9E-eager-%E3%81%AE%E6%84%8F%E5%91%B3%E3%81%AF%E4%BD%95%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%81%8B-%E4%BE%8B-eager-load

https://stackoverflow.com/questions/26005994/laravel-with-method-versus-load-method