N+1問題とは何か
N+1問題とは無駄に(必要よりも多く)SQLを実行させてしまい、パフォーマンスを低下させてしまう問題のことを言います。
例えば下記のコードを見てみましょう。
UserとMessageのモデルがあるとして、
# app/modules/user.rb class User < ActiveRecord::Base has_many :messages end
# app/models/message.rb class Article < ActiveRecord::Base belongs_to :user end
上の例では、Userが複数のMessageを持っています。
続けてViewでMessageのcontent(文章)を表示するとしましょう。
# app/controllers/users_controller.rb class MessagesController < ApplicationController def index @messages = Message.all end end
# app/views/articles/index.html.haml - @messages.each do |message| = message.content = message.user.name
このコードを実行するとSQLのログは下記になります。
SELECT 'messages'.* FROM 'messages' # Message.all の実行 SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 1 LIMIT 1 # message.user.name を実行する際に走る SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1
このようにmessageの数だけ
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = n LIMIT 1
が実行されてしまいます。
これがパフォーマンスの低下につながり、N+1問題と言われています。
解決方法
1対Nの場合
1対Nはここまで説明してきたUserとMessageの関係です。
この場合コントローラで@messagesを取得する際に、
@messages = Message.all.includes(:user)
SELECT 'message'.* FROM 'messages' SELECT 'users'.* FROM 'users' WHERE 'users'.'id' IN (1, 2)
の2つの SQL が発行されるだけになり、N+1 問題が解決します。
まとめ
N+1問題の解決方法は上記の通りです。N対Nの記述方法については別の記事に書く予定です。
N+1問題は確かにパフォーマンスを下げますが、コードの可読性も下げることになります。場合によってはそこまで気にする必要もないパターンもあるようなので、これから学んでいければと思っています。
参考記事:Rails で includes して N+1 問題対策 - Qiita