#N+1問題とは
たとえば、.eachメソッド等を使って、複数(N個)のデータを取り出そうとしたとき、N回SQLが発行されてしまい、パフォーマンスの低下につながる問題。
例
このような、1対多のUseとMessageモデルがあったとする。
app/model/user.rb
class Message < ApplicationRecord
has_many :messages
end
app/model/message.rb
class User < ApplicationRecord
belongs_to :user
end
コントローラにて、ビューに渡す値を取得
app/controllers/message_controller.rb
def index
@messages = Message.all
end
contentとそれに紐づくnameを表示
app/views/index.html.erb
<% @messages.each do |m| %>
<span><%= m.content %> </span>
<span><%= m.user.name %> </span>
<% end %>
その時のSQLはというと、こんな感じでレコードごとにSQLが発行されてしまいます。
Message Load (3.7ms) SELECT `messages`.* FROM `messages` # +1の部分
↳ app/views/messages/index.html.erb:5
User Load (9.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
↳ app/views/messages/index.html.erb:8
User Load (1.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1
↳ app/views/messages/index.html.erb:8
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 [["id", 3], ["LIMIT", 1]]
↳ app/views/messages/index.html.erb:8
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 [["id", 3], ["LIMIT", 1]]
↳ app/views/messages/index.html.erb:8
・
・
・
(N個分続く)
#対策
gem bulletを導入する。
そうすると、こんな感じのSQLが発行され、パフォーマンスが向上します。
Message Load (1.1ms) SELECT `messages`.* FROM `messages`
↳ app/views/messages/index.html.erb:5
User Load (0.9ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4)