はじめに
Laravel や Rails のようなフレームワークでは、N+1問題を回避するために eager loading を使用する事が一般的です。本記事では、Laravel の eager loading に関する小ネタをいくつか書いていきます。
Laravel の eager loading 自体に関しては、ドキュメントに詳しく記述されていますので、まずは一通り目を通す事をオススメします。
- 英語: Eloquent: Relationships – Laravel – The PHP Framework For Web Artisans
- 日本語: Eloquent:リレーション 5.8 Laravel
件数を事前に取得する
実は、上述のドキュメントの eager loading の項ではなく、1つ前の項(Querying Relations -> Counting Related Models)に記載があるのですが、eater loading の書式(::with('table')
)と似たような書式(::withCount('table')
)で、子テーブルの件数を事前に取得する事が出来ます。
分かりやすい使用例としては、ブログ記事一覧を表示する際に、各記事についているコメント数も合わせて表示する、というのがあります。(上述のドキュメントでも、この例が載っています。)
例えば、以下のようなコードがあった場合、
$posts = App\Post::withCount('comments')->take(10)->get();
実行される SQL は以下のようなものです。
select `posts`.*
, (select count(*) from `comments` where `posts`.`id` = `comments`.`post_id`) as `comments_count`
from `posts` limit 10
lazy にやるには
lazy に eager loading をする方法はドキュメントに記載されていますが、件数の取得を lazy に行うにはどうすれば良いでしょうか。
Laravel 5.8 からは loadCount
メソッドというのが追加されており、load
で lazy に eager loading を行うのと同じように、件数も lazy に取得する事ができます。以下の SO の投稿で知りました。
Laravel Eloquent Lazy Eager Load Count – Stack Overflow
複数のカラムでリレーションしている場合
Rails や、Rails に影響を受けている Laravel のようなフレームワークでは、複合主キーは使わず、連番のサロゲートキーを使う事が(それ自体の是非や好き嫌いはさておき)ベストプラクティスです。
ただ、現実の場面では、複数のカラムの値を使って子テーブルを読み込まなければいけないケースは多々あると思います。普通に読み込むだけであれば hasMany
や hasOne
の後ろに where
で条件を絞れば良いのですが、それだと eager loading が正しく動作しません。
それを解決するために、以下のようなモジュールがありました。
topclaudy/compoships: Multi-columns relationships for Laravel 5’s Eloquent
この辺りの話題は SO の以下のスレッドを参照して下さい。
Laravel Eloquent: multiple foreign keys for relationship – Stack Overflow
まとめ
Web アプリのパフォーマンス問題を解決するには、eager loading を上手く使う事が必須です。
Laravel の eager loading は機能が豊富で、標準の機能だけで大半の問題は解決できると思いますので、まずはドキュメントを一通り読む事をお勧めします。
複合キーの問題は、この手のフレームワークを使う時に定番の話題だと思います。Laravel を使う場合は、できる限り複合キーを排除した方が良いのですが、複合キーで参照している子テーブルを eager loading したい場合は、Comphoships モジュールの導入を検討してみて下さい。