【Laravel】LaravelのN+1問題とは、その解決方法

##はじめに
**Laravel: 7.30.4**
**PHP: 7.2.3**

N+1問題についてなぜこの問題が発生するのか、初学者が簡単に理解できるように書いていきたいと思います。

##準備
まず、適当にリレーションを張ったモデルを用意します。今回は`Author`(著者)モデルと`Article`(記事)モデルを用意して、`Author`が複数の`Article`を持つようにします。

```php:Article.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function Author()
    {
        return $this->belongsTo('App\Author');
    }
}
```
※Authorモデルには特になにも書かなくて大丈夫です。

次にテーブルを用意して適当にデータを挿入していきましょう

テーブル作成はこちら↓のURLが参考になります。
https://qiita.com/yukibe/items/f05bf5e829a9a05616f7

ダミーデータを登録する際はこちら↓のURLが参考になります
https://www.larajapan.com/2021/05/03/bulk-insert%E3%81%A7%E5%A4%A7%E9%87%8F%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92db%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B/


ルーティングも書いておきましょう↓

```php:web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/article', 'ArticleController@handle');
```

##N+1問題を発生させる
ここからが本題です、まず実際にN+1問題を発生させてみましょう！

そもそもN+1問題とは何なのでしょうか？
N+1問題とは、例えば、`Author`(著者)が複数の`Article`(記事)を持っている時にこんな処理を書くことがあると思います↓

```php:ArticleController.php
<?php

namespace App\Http\Controllers;

use App\Article;

class ArticleController extends Controller
{
    public function handle()
    {
        $articles = Article::all(); // ①記事を全件取得する
        
        // ②取得した記事をループで回して一件ずつ著者の名前を出力する
        foreach($articles as $article) {
            echo $article->author->name;
        }
    }
}
```

※普段はコントローラから`echo`で出力はしないと思いますが今回は参考として書きます。

上記のコードでは下記のことが起こります。
・①記事を全件取得する
・②記事の数だけ著者の名前を取得する
ということが起こります。

`記事を全件取得する : 記事の数だけ著者の名前を取得する = 1 : N `
という感じです

つまり、著者の名前を取得したい時に、記事の数(N回)だけクエリを発行されるということです。
これが、記事の数が少ないときはパフォーマンスにさほど影響は与えないのですが、もし、記事が10万件あったら処理のスピードがかなり遅くなってしまいます。これがN+1問題です。

実際に上記のコードが実行された時に発行されたクエリを見てみましょう↓
※ダミーデータを100件登録してあります。

```php
array:101 [▼
  0 => array:3 [▼
    "query" => "select * from "articles""
    "bindings" => []
    "time" => 0.36
  ]
  1 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [▶]
    "time" => 0.09
  ]
  2 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [▶]
    "time" => 0.06
  ]
  3 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [▶]
    "time" => 0.06
  ]
  4 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [▶]
    "time" => 0.06
  ]
  5 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [▶]
    "time" => 0.06
  ]
  ※以下省略
  6 => array:3 [▶]
  7 => array:3 [▶]
  8 => array:3 [▶]
  9 => array:3 [▶]
  10 => array:3 [▶]
  11 => array:3 [▶]
  12 => array:3 [▶]
  13 => array:3 [▶]
  14 => array:3 [▶]
  15 => array:3 [▶]
  16 => array:3 [▶]
  17 => array:3 [▶]
  18 => array:3 [▶]
  19 => array:3 [▶]
  20 => array:3 [▶]
  21 => array:3 [▶]
  22 => array:3 [▶]
  23 => array:3 [▶]
  24 => array:3 [▶]
  25 => array:3 [▶]
  26 => array:3 [▶]
  27 => array:3 [▶]
  28 => array:3 [▶]
  29 => array:3 [▶]
  30 => array:3 [▶]
  31 => array:3 [▶]
  32 => array:3 [▶]
  33 => array:3 [▶]
  34 => array:3 [▶]
  35 => array:3 [▶]
  36 => array:3 [▶]
  37 => array:3 [▶]
  38 => array:3 [▶]
  39 => array:3 [▶]
  40 => array:3 [▶]
  41 => array:3 [▶]
  42 => array:3 [▶]
  43 => array:3 [▶]
  44 => array:3 [▶]
  45 => array:3 [▶]
  46 => array:3 [▶]
  47 => array:3 [▶]
  48 => array:3 [▶]
  49 => array:3 [▶]
  50 => array:3 [▶]
  51 => array:3 [▶]
  52 => array:3 [▶]
  53 => array:3 [▶]
  54 => array:3 [▶]
  55 => array:3 [▶]
  56 => array:3 [▶]
  57 => array:3 [▶]
  58 => array:3 [▶]
  59 => array:3 [▶]
  60 => array:3 [▶]
  61 => array:3 [▶]
  62 => array:3 [▶]
  63 => array:3 [▶]
  64 => array:3 [▶]
  65 => array:3 [▶]
  66 => array:3 [▶]
  67 => array:3 [▶]
  68 => array:3 [▶]
  69 => array:3 [▶]
  70 => array:3 [▶]
  71 => array:3 [▶]
  72 => array:3 [▶]
  73 => array:3 [▶]
  74 => array:3 [▶]
  75 => array:3 [▶]
  76 => array:3 [▶]
  77 => array:3 [▶]
  78 => array:3 [▶]
  79 => array:3 [▶]
  80 => array:3 [▶]
  81 => array:3 [▶]
  82 => array:3 [▶]
  83 => array:3 [▶]
  84 => array:3 [▶]
  85 => array:3 [▶]
  86 => array:3 [▶]
  87 => array:3 [▶]
  88 => array:3 [▶]
  89 => array:3 [▶]
  90 => array:3 [▶]
  91 => array:3 [▶]
  92 => array:3 [▶]
  93 => array:3 [▶]
  94 => array:3 [▶]
  95 => array:3 [▶]
  96 => array:3 [▶]
  97 => array:3 [▶]
  98 => array:3 [▶]
  99 => array:3 [▶]
  100 => array:3 [▶]
]
```
1番最初に実行されているのが、記事の全件取得のクエリで、その後にループで著者の名前を1つづつ取得しているのがわかると思います。掛かっている時間がだいたい60ミリ秒です。

これはLaravelの**動的プロパティは遅延ロード**されるという性質が原因で引き起こされるのですが、今回その説明は割愛します。

## N+1問題を解決していく

では、さきほどのN+1問題を解決していきましょう。

Laravelではとても簡単にN+1問題を解決できるメソッドが用意されています。`with()`というメソッドです。

```php
$articles = Article::with('author')->get();
```
このように書くことで、`Article`(記事)の全件取得をする時に、ついでにリレーションが張られている`Author`(著者)の情報も一緒に取得してくれるような感じです。
これを**Eagerロード**といいます。

先程のコードを`with()`を使用して書き換えてみましょう。

```php:ArticleController.php
<?php

namespace App\Http\Controllers;

use App\Article;

class ArticleController extends Controller
{
    public function handle()
    {
        $articles = Article::with('author')->get();
        
        foreach($articles as $article) {
            $article->author->name;
        }
    }
}
```

`$articles = Article::all();`
の部分が
`$articles = Article::with('author')->get();`
に変わっただけです。

ではこちらを実行してみましょう。発行されたクエリはこんな感じです↓

```php
array:2 [▼
  0 => array:3 [▼
    "query" => "select * from "articles""
    "bindings" => []
    "time" => 0.39
  ]
  1 => array:3 [▼
    "query" => "select * from "authors" where "authors"."id" in (1)"
    "bindings" => []
    "time" => 0.07
  ]
]
```

なんと発行されたクエリは2件だけですね。かなりすっきりしたのがわかると思います。
掛かっている時間も0.46ミリ秒とかなり短縮されています。

これでN+1問題が解決できました。