こんにちは、らいか(@hideiwa1)です。
今回は、DBを扱う上で注意するべき「N+1問題」について解説します。
N+1問題とは
リレーションを使用する場合、関連するモデルには「遅延読み込み」という特性があります。
「遅延読み込み」とはどういう事かというと、
プロパティにアクセスされるまで、データを取得しない
つまり、リレーションをしただけではクエリを実行しないという事です。
実際のコードで、どのような事が起きるのかを見てみましょう。
「User」と「Item」が「1対多」でリレーションしているとします。
このとき、すべてのuserとitemを取得すると、以下の通りになります。
$user = User::all();
foreach($users as $user)
{
$user->items;
}
この時、どのようなクエリが実行されているかというと、
select * form Users;
select * from Items where Items.user_id = 1 and Items.user_id is not null;
select * from Items where Items.user_id = 2 and Items.user_id is not null;
…
このように、
・まず「users」テーブルより全件を取得
・次に、user_id毎に「Items」テーブルから一致するものを取得
という流れになっています。
この時、「users」が全10件あったとすると、
・「users」テーブルより全件を取得 → 1回
・user_id毎に「Items」テーブルから一致するものを取得 → 1~10で10回
合計で11回、クエリを実行することになります。
これが「N+1問題」となります。
(10件を取得するのに、11回クエリを実行する)
解決方法は?
正直なところ、対象が10件程度でしたらそれほど影響はありません。
しかし、これが100件、1000件と増えた場合はどうでしょうか?
クエリの数が多くなれば、その分処理速度が低下することになります。
そのため、基本的にN+1問題は避けなければなりません。
N+1問題の解決方法は、「with」メソッドを使用する方法です。
withメソッドには、「Eagerロード(積極的読み込み)」という特徴があります。
Eagerロードでは、データが事前に取得されます。
先ほどと同じ例を使って、流れを見てみましょう。
$user = User::with('item')->get();
foreach($users as $user)
{
$user->items;
}
この際に実行されるクエリは、
select * form Users;
select * from Items where Items.user_id in (1, 2, ・・・);
このように、
・まず「users」テーブルより全件を取得
・次に、「Items」テーブルから全件を取得
と、2つへ削減されました。
そして、件数が増えてもクエリが増えることはありません。
まとめ
リレーションをする場合は、「with」メソッドを使用する方が良い。