はじめに
N + 1問題については、とても参考になるまとまった記事がたくさんあるのですが、自分なりに簡単にまとめてみました。解釈が間違っていましたらご指摘ください。
N + 1問題とは
簡単に言えば、SQLを重複して発行してしまうこと。
例えば投稿アプリで投稿一覧ページがあった場合にユーザーの名前を表示したい場合
def index
@posts = Post.all
end
<% @posts.each do |post| %>
<%= post.user.name %>
<% end %>
のようにすると思います。
この場合発行されるSQLは以下のようになります。
-
@posts = Post.all
で投稿を全て取得 -
post.user.name
で投稿に紐づくユーザーを取得
2.の、投稿に紐づくユーザーを取得するとき、画面に表示される投稿の数分、投稿に紐づくユーザーを一回一回SQLを発行して取得してきます。
つまり投稿が10件の場合、まずPost
を全て取得するSQLと、投稿の数分(N)のSQLが発行されることになり、これが「N + 1」問題と呼ばれています。
これではデータの数が多いほど、パフォーマンスが低下するのは明白です。
そのため、N + 1問題が起きてしまった場合はpreload
メソッドか、eager_load
メソッドを使用することで解決します。
preload
preload
メソッドを使用して引数にアソシエーションを渡すことで、Postのデータとともに関連するuserのデータも取得してくれます。
SQLの発行回数は、Postのデータ取得で1回、Postに関連するuserの取得で2回となります。
def index
@posts = Post.preload(:user)
end
eager_load
eager_load
メソッドも、同様にアソシエーションを取得してくれます。
こちらはテーブル同士を一つに結合させてしまい、viewで呼び出す際にその結合したテーブルから得たデータを取得します。
SQLは1回だけです。
def index
@posts = Post.eager_load(:user)
end
どちらがいいのか
調べてみた感じでは、「こっちを使った方がいい」というような偏った意見は見かけませんでした。
テーブルを結合させるのかさせないのかなど、アプリが複雑で大規模なものであれば考える必要がある印象です。個人開発であれば取り敢えずeager_load
メソッドを使っておけばいいのかな?と思います。
参考
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
【Ruby on Rails】n+1 問題とは?