N+1問題 解消方法 (Rails)

f:id:utouto97:20210704222355j:plain

対象のN+1問題

前回、触れたN+1問題を今回は解消します。

utouto97.hatenablog.com

↓で起こっている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という選択肢もあるようです。
違いについては、↓にまとまっています。

qiita.com

終わり