N+1問題 解消方法 (Rails)
対象のN+1問題
前回、触れたN+1問題を今回は解消します。
↓で起こっているN+1が対象です。
<% @todos.each do |todo| %> <div> <%= todo.title %> : <%= todo.user.name %> </div> <% end %>
N+1問題を解消する
RailsでN+1を解消するには、includes
を使えばいいです。
変更前は、↓のようにTodo一覧を取得しています。
def index @todos = Todo.all end
これにincludes(:user)
をつけるだけで、N+1問題が解消されます。
def index @todos = Todo.all.includes(:user) end
ログを確認してみます。
rails_1 | Processing by TodosController#index as HTML rails_1 | Rendering layout layouts/application.html.erb rails_1 | Rendering todos/index.html.erb within layouts/application rails_1 | (0.1ms) SELECT sqlite_version(*) rails_1 | ↳ app/views/todos/index.html.erb:3 rails_1 | Todo Load (0.1ms) SELECT "todos".* FROM "todos" rails_1 | ↳ app/views/todos/index.html.erb:3 rails_1 | User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?) [["id", 1], ["id", 2], ["id", 3], ["id", 4]] rails_1 | ↳ app/views/todos/index.html.erb:3 rails_1 | Rendered todos/index.html.erb within layouts/application (Duration: 32.2ms | Allocations: 8984) rails_1 | [Webpacker] Everything's up-to-date. Nothing to do rails_1 | Rendered layout layouts/application.html.erb (Duration: 94.9ms | Allocations: 12659) rails_1 | Completed 200 OK in 99ms (Views: 97.5ms | ActiveRecord: 1.8ms | Allocations: 14234)
Todoの取得とUserの取得で、計2回のクエリしか発行されていません。
これにより、N+1問題を解消することができました。
includes
のほかに、preload, eager_load, joins
という選択肢もあるようです。
違いについては、↓にまとまっています。
終わり