迷い人

日々勉強。勉強の先に何か見つかるといいなぁ

【rails】n+1問題の解決

本日はn+1問題を解決していこうと思います。

 

いきなりですがn+1問題とは

 

何度もSQLが発行されてしまい、パフォーマンスが低下してしまう現象

 

です。

 

実はこれまで作成してきたtask管理アプリで起きていました。

どこで起きていたかというとtask管理一覧です。

 

本日のお品書き

  1. n+1問題は起きていることを確認する
  2. n+1問題を解決する

 

n+1問題が起きていることを確認する

では、まずn+1問題がどういうものか確認してみましょう。

確認方法は簡単で、コンソールを見るだけです。

 

コンソールで「rails s」と打ちこむとhttp://localhost:3000でtask管理アプリにアクセスできます。

アクセス後、タスク一覧画面を表示した時にどのようなSQLが発行されているかがコンソールで確認できます。

 

具体的には以下のような内容がログとして残っていることがわかります。

 

Task Load (0.4ms)  SELECT `tasks`.* FROM `tasks`
  ↳ app/views/tasks/index.html.erb:33
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  ↳ app/views/tasks/index.html.erb:37
  CACHE User Load (0.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/tasks/index.html.erb:37
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
  ↳ app/views/tasks/index.html.erb:37
  CACHE User Load (0.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/tasks/index.html.erb:37
  CACHE User Load (0.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1  [["id", 2], ["LIMIT", 1]]
  ↳ app/views/tasks/index.html.erb:37
  CACHE User Load (0.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1  [["id", 2], ["LIMIT", 1]]
  ↳ app/views/tasks/index.html.erb:37
  CACHE User Load (0.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1  [["id", 2], ["LIMIT", 1]]

 

SELECTで始まるSQL構文が大量に発行されています。

これはタスク一覧画面で、タスク単位でユーザ名を表示させる毎にSQLが発行されていることを表しています。

 

n+1問題を解決する

この問題を解決する方法は簡単で、indexアクションを以下のように修正します。

ファイルはapp/controllers/task_controller.rb 

 

task_controller.rb
class TasksController < ApplicationController #省略 def index @tasks = Task.all.includes(:user) end #省略 end

 

 includes(:user)を追加するだけです。

こうする事で事前にuser情報を読み込むことができて、taskの数だけSQLが発行されることが無くなります。実際にコンソールを確認すると以下のようになっているはずです。

 

Task Load (0.5ms)  SELECT `tasks`.* FROM `tasks`
  ↳ app/views/tasks/index.html.erb:33
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 2)
  ↳ app/views/tasks/index.html.erb:33

 

user_idが1と2のユーザが事前にselectされています。

 

参考

Rails で includes して N+1 問題対策 - Qiita