Day 4・EloquentとリレーションとN+1問題
クエリビルダとコレクションの違い
絞り込みをDB側でやるか、PHP側でやるか。性能の基礎となる「どこで絞るか」の意識を持つ。
クエリビルダとコレクション
Eloquentには、似ているようで働く場所が違う2つの世界があります。「クエリビルダ」は、まだDBに投げる前のSQLを組み立てる段階で、whereやorderByはここでSQLの条件になります。一方「コレクション」は、getなどでDBから取り出した後のデータ(PHPのメモリ上の配列のようなもの)で、そこでのfilterやsortはPHP側の処理になります。
補足
見分け方の目安: getやall、findで結果を取り出す前がクエリビルダ(SQLになる)、取り出した後がコレクション(PHPで処理される)です。
どこで絞るかで性能が変わる
同じ「公開済みの投稿だけ欲しい」でも、DB側で絞るかPHP側で絞るかで負荷が大きく変わります。全件をPHPに読み込んでから絞ると、DBもメモリも無駄に使います。
悪い例(全件取ってからPHPで絞る)
全件をメモリに載せてからfilter。DB転送量もメモリも無駄。
// 全投稿をDBから取り出す(公開・非公開すべて)
$posts = Post::all();
// PHP側(コレクション)で絞り込む
$published = $posts->filter(function ($post) {
return $post->is_published;
});
// 100万件あれば100万件を読み込んでから捨てることになる良い例(DB側で絞ってから取り出す)
whereで条件をSQLに乗せ、必要な行だけDBから受け取る。
// DB側(クエリビルダ)で絞ってから取り出す
$published = Post::where('is_published', true)->get();
// 必要な行だけがメモリに載る。転送量も少ない-- 条件が WHERE に乗るので、DBが必要な行だけ返す
SELECT * FROM posts WHERE is_published = 1;なぜ「DB側で絞る」のが基本なのか
DBは大量データの絞り込み・並べ替えに最適化された専用エンジンで、インデックスも使えます。PHP側で絞ると、まず全件をネットワーク経由で受け取り、メモリに載せ、そこから捨てる、という無駄が発生します。『絞り込みや集計はできるだけDBに任せ、PHPには必要な分だけ持ってくる』——これが性能設計の基礎です。
件数の多いテーブルへの向き合い方
- 一覧はpaginateでページ分割し、一度に全件を読まない。
- 絞り込み条件(where)・並べ替え(orderBy)はできるだけクエリビルダ側=SQLに寄せる。
- 件数だけ欲しいならcount()、存在確認だけならexists()を使い、全件取得を避ける。
- 集計(合計・平均)もsum()やavg()でDB側に任せる。
- 大量データを1件ずつ処理するときはchunkやcursorでメモリを守る。
落とし穴
コレクションのメソッド(filter・sort・whereなど)は便利ですが、それはすでにメモリに載ったデータへの操作です。「Post::all()->where(...)」のように書くと、先に全件を取ってしまうので、DBで絞る「Post::where(...)->get()」とは負荷が全く違います。メソッド名が似ているぶん取り違えやすいので注意しましょう。
面接ではこう言う
「クエリビルダはSQLになる前、コレクションは取り出した後のPHP上のデータで、絞り込みをどちらでやるかで性能が変わります。私は絞り込み・並べ替え・集計はできるだけwhereやcount、sumでDB側に寄せ、PHPには必要な分だけ持ってくる方針です。一覧はpaginateで全件取得を避けます」と言えると、性能を意識していると伝わります。
読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。
理解度チェック
答えられれば、面接で説明できる状態に一歩近づきます。
「Post::all()->filter(...)」と「Post::where(...)->get()」の違いとして、最も適切なものはどれか。
「絞り込みはできるだけDB側でやるべき」と言われる理由を説明せよ。
記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。