preload/eager_load/includes
結論
まずpreloadを検討する
has_manyアソシエーションをeager_loadするとスロークエリが発生しやすいのを念頭におく
概要
N+1問題を解消するために予めデータを1or2クエリでまとめて読み込んでおく eager loadingはメモリ上にキャッシュを乗っけるのでメモリ使用量増加
table:まとめ
メソッド associationをキャッシュ クエリ 用途
joins しない 単数 絞り込み
eager_load する 単数 キャッシュと絞り込み
preload する 複数 キャッシュ
includes する 場合による キャッシュ、必要なら絞り込み
joins + preload する 複数 キャッシュと絞り込み
joins
例:User.joins(:posts).where(posts: { id: 1 })
デフォルトinner join
JOINして条件を絞り込みたいが、JOINするテーブルのデータを使わない場合はjoinsを使う
preload
例:User.preload(:posts)
code:sql
# SELECT users.* FROM users
# SELECT posts.* FROM posts WHERE posts.user_id IN (1, 2, 3, ...)
クエリ数2
指定したassociationを複数のクエリに分けて引いてキャッシュ
ユースケース
複数のassociationをeager loadingする
JOINしたくないでかいテーブルを扱う
has_many なアソシエーションについてはpreload
Joinしていないので条件で絞り込めない
preloadしたテーブルによって絞り込もうとすると例外
User.preload(:posts).where(posts: { id: 1 })
これは例外投げる
IN句が大きくなり過ぎないように気をつける
ページングなど
eager_load
例:User.eager_load(:posts)
User.eager_load(:posts).where(posts: { id: 1 })
associationをLEFT OUTER JOIN
select句にaccociationのカラム全て列挙されるクエリが発行される
クエリ数1
associationはキャッシュされる
belongs_to, has_one アソシエーションについては eager_load
/icons/point.icon1対1の関係であればleft joinしても行数が増えないため
逆にhas_manyな関係だとleft joinで行数が増えてしまい、他のメソッドのパフォーマンスが大幅に落ちることもある→distinctしなければいけないため
/icons/重要.icon/icons/重要.icon/icons/重要.iconhas_manyアソシエーションをeager_loadするとスロークエリが発生しやすい
eager_loadが有効なケースについて
関連データでのページネーション
code:ruby
# JOINした結果全体でページネーションしたい場合
User.eager_load(:posts)
.where(posts: { status: 'published' })
.order('posts.created_at DESC')
集約クエリ
code:ruby
# 関連データを含めた集計
User.eager_load(:posts)
.group('users.id')
.having('COUNT(posts.id) > 5')
includes
いい感じに判断してpreloadとeager_loadのどちらかになる
分かりづらいから基本使わない
joins + preload
絞り込みはjoinsで効率的に行う
データ取得はpreloadで不要に取りすぎないように安全に行う
code:ruby
User.joins(:posts)
.where(posts: { status: 'published' })
.preload(:posts, :profile)
参照