N+1問題
はじめに
Ruby on RailsのN+1問題に関して調べたので、その忘備録
N+1問題とは
テーブルに対してデータ検索を行うとき、不必要に大量のSQL(N+1件のSELECT文)を発行してしまい、パフォーマンスが低下してしまう問題のこと。
1:多などモデルの関連付けをしている場合に起きやすい
例) UserとArticleの二つのモデルがあるとする。
app/models/user.rb
class User > ActiveRecord::Base has_many :articles end
app/models/article.rb
class Article > ActiveRecord::Base belongs_to :user end
このようにアソシエーションを記述した状態で、以下のようなアクションを設定する。
app/controllers/users_controller.rb
class ArticlesController < ApplicationController def index User.all.each do |u| article_name = u.articles.name end end end
この状態でこのアクションを呼び出すと、
SELECT 'users'.* FROM 'users' # User.all の実行 SELECT 'articles'.'name' FROM 'articles' WHERE 'articles'.'id' = 1 LIMIT 1 # user.articles.name を実行する際に走る SELECT 'articles'.'name' FROM 'articles' WHERE 'articles'.'id' = 2 LIMIT 1 ...
と、Articleの数だけ
SELECT 'articles'.name FROM 'articles' WHERE 'articles'.'id' = n LIMIT 1
が走ってしまう。
なぜ生じるのか?
上記の例では、まずUser
のデータを全部取得したあと、article_name = u.articles.name
毎にArticleのデータベースにアクセスしてデータをとってくるように記述している。
そのせいで、「Userテーブルに対して一回+Articleテーブルに対してUser数ぶん」の回数のSQLが発行されることになる。
解決方法
includesメソッドを使う。
app/controllers/users_controller.rb
class ArticlesController < ApplicationController def index User.includes(:article).all.each do |u| article_name = u.articles.name end end end
このように、「モデルA.includes(:モデルB)」とすると、Aに基づくBをまとめて取得できるようになる。
上記の例だと、 「Userテーブルに対して一回+Articleテーブルに対してUser数ぶんをまとめて一回」の2回のSQLで済むようになる。