Railsでモデルの絞り込みなどをする場合にN+1問題に対処する必要があるが、よく includes
で解決している記事を見る。しかしどうも万能ではないのであまり使わない方がいいことがわかった。
artist
モデルをscaffoldで作成rails g scaffold artist name:string
# artist.rb
class Artist < ApplicationRecord
has_many :albums
end
album
モデルを artist
の子モデルとしてscaffoldで作成rails g scaffold album title:string artist:references
# album.rb
class Album < ApplicationRecord
has_many :albums
belongs_to :artist
end
# albums_controller.rb
class AlbumsController < ApplicationController
def index
@albums = Album.all
end
end
# albums/index.html.erb
<div>
<% @albums.each do |album| %>
<p><%= album.title %></p>
<p><%= album.artist.name %></p>
<% end %>
</div>
この場合だとSQLは
SELECT "albums".* FROM "albums"
# Album.allの実行
SELECT "artists".* FROM "artists" WHERE "artists"."id" = 1 LIMIT 1
SELECT "artists".* FROM "artists" WHERE "artists"."id" = 2 LIMIT 1
SELECT "artists".* FROM "artists" WHERE "artists"."id" = 3 LIMIT 1
# artist.nameをartistの数SQLを発行してしまう
となってしまい、N+1問題と言われている。
@albums = Album.all.includes(:artist)
SELECT "albums".* FROM "albums"
SELECT "artists".* FROM "artists" WHERE "artists"."id" IN (?, ?, ?)
# artistテーブルから全てのidを取得してからまとめて1回で実行している
include
についてinclude
メソッドはアソシエーションによって preload
もしくは eager_load
が呼ばれているので、データの数が多くなるにつれて意図せず動作が遅くなってしまうことがある。
なので状況に応じて preload
もしくは eager_load
を使い分けた方が良い。
また複数のアソシエーションを渡した場合は必ずどちらか一方の挙動になる
preload
上の例で試してみる
preload
を使う@albums = Album.all.preload(:artist)
SELECT "albums".* FROM "albums"
SELECT "artists".* FROM "artists" WHERE "artists"."id" IN (?, ?, ?)
# includes のときと同じSQLを発行している
eager_load
を使う@albums = Album.all.eager_load(:artist)
SELECT
"albums"."id" AS t0_r0,
"albums"."title" AS t0_r1,
"albums"."artist_id" AS t0_r2,
"albums"."created_at" AS t0_r3,
"albums"."updated_at" AS t0_r4,
"artists"."id" AS t1_r0,
"artists"."name" AS t1_r1,
"artists"."created_at" AS t1_r2,
"artists"."updated_at" AS t1_r3 FROM "albums" LEFT OUTER JOIN "artists" ON "artists"."id" = "albums"."artist_id"
# JOINを使っている
ActiveRecordのincludes, preload, eager_load の個人的な使い分け | Money Forward Engineers' Blog
ActiveRecordのincludesは使わずにpreloadとeager_loadを使い分ける理由 - Qiita