LoginSignup
0
3

More than 1 year has passed since last update.

includesメソッドで良い?RailsでN+1問題を解決する方法!

Last updated at Posted at 2021-05-30

はじめに

RailsでのN+1対策についての記事になります

N+1問題とは?

N+1問題とは、データベースからデータを取り出す際に、無駄なSQLが発行されてパフォーマンスが落ちるという問題です。

どのような時に起こるのか?

例として、UserモデルとAddressモデルを用意して考えます。
以下のような関連を持つテーブルからデータを取り出すとします。

address.rb
class User < ApplicationRecord
  has_one :address
end
user.rb
class Address < ApplicationRecord
  belongs_to :user
end
# controller側
 @users = User.all

# view側
 @users.each do |user| 
  user.address.city
 end 

userに紐づくaddressのcityを取り出す記述になります。
こちらがN+1問題が発生しているコードになります。

N+1問題 = SQLが無駄に発行されている

具体的に説明すると、userの情報を全て取得するためにSQLを1回発行、その後にループ件数分のSQLの発行が行われます。
userに紐づくaddress情報が10件あったとすれば合計11回のSQLを発行していることになっています。
実際にページにアクセスした際のコンソールを見てもらえればSQLが多く発行されていることが分かります!

これが万単位のデータを扱うことになるとパフォーマンスに直結してくるため改善する必要がでてきます!

ではどう改善すればいいの?

初めのuser情報を取得する時に、それに紐づくaddress情報も取得できれば解決します!
ループ処理の前にデータを事前に読み込んでおくということです!

Railsではその方法としてincludesメソッドが用意されています。
実際にこのように使用します。

# controller側
 @users = User.all

# view側
 @users.includes(:address).each do |user| 
  user.address.city
 end 

こうすることでuserに紐づくaddress情報を事前に取得してくれます!
コンソールで確認してみるとSQLの発行数が減っていることが確認できます。

ではこれで解決!

と言いたいところですが、ここからが本題になります。

includes以外にもメソッドが用意されている

RailsにはN+1問題を解決するメソッドとしてincludesの他にpreloadeager_loadがあります。

preloadメソッドを使用
 @users.preload(:address).each do |user| 
  user.address.city
 end 
eager_loadメソッドを使用
 @users.eager_load(:address).each do |user| 
  user.address.city
 end 

使い方としてはincludesと遜色ありません

では結局どれをどう使えばいいの?という話になってきますね

実はpreloadとeager_loadを集約したメソッドがincludesになります!
つまりincludesを使用すると、内部でpreloadとeager_loadの挙動を自動で振り分けてくれます。

includesを用いると、デフォルトではpreloadと同様の挙動
他テーブルを結合しているか、関連先のテーブルで絞り込みを行っている場合はeager_loadと同じ挙動となる

じゃあ便利そうなincludesを使っておけば間違いないね!
という話ではありません!

includesはなるべく利用しない方が良い

結論:includesはなるべく使用せずに、preloadとeager_loadを使い分けよう!という話です。
理由:includesはクエリが状況によって変わるためコントロールが難しい。

データが膨大になってくると意図しない挙動を起こしてしまう可能性があるということです

preload、eager_loadの使い分け

上記の理由から、基本はpreload、eager_loadを使用する方が良いと考えられます。

では肝心の使い分けについてですが
難しい話は割愛し、どのように使い分けるのが適切なのかだけ書いておきます。

preload

多対多のアソシエーションの場合に有効(has_manyなアソシエーション)
データ量が大きいと考えられる状況であれば、クエリを分割して取得するpreloadの方がレスポンスが早いと考えられます。

eager_load

1対1あるいは多対1のアソシエーションの場合に有効(has_one、belongs_toなアソシエーション)
?対1で関連付けられている場合は、外部結合しても取得するレコード数が変わらないため効率が良いと考えられます。

ぜひこちらを参考に使い分けを行い、N+1問題を解決してみてください!

そもそもどこでN+1問題が起きているか分からない

そんな方にはN+1クエリを自動で検出してくれるBulletという便利なgemがあります。
Bulletを使うと、RailsのログやブラウザでN+1であることを確認できます。

gem "bullet"

使用方法については別の記事を参考にしてみてください!

参考文献

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3