N+1問題
前回、ORMについてまとめました。
SQLを書かなくてもDB操作を簡単にできて便利なORMですが、
N+1問題という問題があります。
N+1問題とは
N+1問題とは、ループ処理の中で、1回ずつクエリを発行してしまう問題です。
これにより、無駄なクエリが発行され、パフォーマンスの低下を招きます。
より具体的にいうと、Todoの一覧を取得し、Todo一つずつの担当ユーザー(User)を表示しようとするとき
ループを使って記述すると、合計でN+1回のクエリが発行されます。
- Todoの一覧を取得(1回)
- Todoの担当ユーザーを取得(N回、ループによる)
しかし、あらかじめユーザーの一覧を取得しておき、Todoの担当ユーザーの情報をそこからもってくれば、合計2回のクエリで済みます。
- ユーザーの一覧取得(1回)
- Todoの一覧を取得(1回)
RailsでN+1問題を試す
N+1を確認する
↓の感じで、ループ内部で一つずつ表示してみます。
ちなみに、@todos = Todo.all
としています。
<% @todos.each do |todo| %> <div> <%= todo.title %> : <%= todo.user.name %> </div> <% 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 | Todo Load (0.2ms) SELECT "todos".* FROM "todos" rails_1 | ↳ app/views/todos/index.html.erb:3 rails_1 | User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]] rails_1 | ↳ app/views/todos/index.html.erb:5 rails_1 | Rendered todos/index.html.erb within layouts/application (Duration: 29.9ms | Allocations: 15572) rails_1 | [Webpacker] Everything's up-to-date. Nothing to do rails_1 | Rendered layout layouts/application.html.erb (Duration: 81.8ms | Allocations: 22040) rails_1 | Completed 200 OK in 104ms (Views: 86.5ms | ActiveRecord: 1.5ms | Allocations: 28480)
SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?
がTodoと同じ数だけ発行されています。
CACHE
とついてるので、キャッシュされているかもしれませんね。
終わり