概要
面接の際にN+1問題について問われたのですが、あまりうまく答えられなかったので整理します。
主にLaravelでのN+1問題について記述します。
間違っている部分等ありましたらご指摘いただけると幸いです。
N+1問題
LaravelはEloquent ORMを使用されます。
ORMとはデータベースのレコードをオブジェクトとして直感的に扱えるようにするものです。
生のSQLで書く必要がないため、RDBへアクセスする処理を簡単に行うことができます。
そして、Eloquentのリレーションはデフォルトとして「遅延ロード」されますが、それを「Eagerロード」にすることでN+1問題を解決することができます。
「遅延ロード」と「Eagerロード」
では、遅延ロードとEagerロードとの違いについて見ていきます。
以下の関係のテーブルがあった場合(公式より抜粋)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* この本を書いた著者を取得
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
遅延ロードで全書籍と著者を取得すると以下のようになります。
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
この場合、
echo $book->author->name;
が実行されることで初めてBookとAuthor間のリレーションデータが取得されます。
つまり、echo $book->author->name;が行われるたびに
public function author()
{
return $this->belongsTo('App\Author');
}
にアクセスをしてデータを取得していることとなります。
ですので、上記の例だとBookの数だけselect文が発行されることになってしまいます。
このことをN+1問題と言うみたいです。
そしてN+1問題を解決するためにEagerロードと言うものが存在します。
遅延ロードだと
1.Authorのリレーションデータを持っていないBookモデルコレクションを取得
2.リレーションデータにアクセスするたびにリレーションデータを取得
以下手順2をループ
に対して
1.Authorリレーションデータを事前に取得したBookモデルコレクションを取得
とすることでクエリの発行を最小限に抑えることができます。
LaravelでのEagerロードはwithを使うことで行うことができます。
// 遅延ロード
$books = App\Book::all();
// Eagerロード
$books = App\Book::with('author')->get();
また、N+1問題の検知は以下のようなパッケージがあるみたいです。(まだ触っていないのでなんとも言えないですが)
https://github.com/beyondcode/laravel-query-detector
まとめ
わかっているつもりでプログラムを書いていましたが、改めて調べてみることで全然理解できていないなと痛感しました。
参考文献
公式
オブジェクト関係マッピング
Laravelでリレーションのあるテーブルをwithで取得する【N+1問題】
LaravelでN+1を検出させる