1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】N+1問題とは? includesメソッドで解決する

Last updated at Posted at 2020-04-12

N+1問題とは

N+1問題とは、大量のSQLが実行されて動作が重くなるという問題。

「全てのレコードを取得する1回+N回分SQLが実行される」ために、N+1問題という。

ex.)掲示板一覧ページにアクセスした時

  1. 掲示板(Board)のデータををallで取得(1回)
  2. その各掲示板を投稿したユーザーの名前を表示させたい。
    →Usersテーブルからデータを取得する必要がある。
    →掲示板の数の分だけusersテーブルから名前を取得するSQLを実行する。
    →掲示板の数が10個あれば、10回SQLを実行する。

仮にこれが1万回もSQLを実行した場合、めちゃくちゃ重くなる。

以下のログでは、掲示板を全て取得してから、ユーザーの情報を取得するためのSQLが何度も実行されている。

Started GET "/boards" for ::1 at 2020-03-20 16:52:47 +0900
Processing by BoardsController#index as HTML
  Rendering boards/index.html.erb within layouts/application
  Board Load (1.2ms)  SELECT "boards".* FROM "boards"
   app/views/boards/index.html.erb:17
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   app/views/boards/index.html.erb:34
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
   app/views/boards/index.html.erb:34
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
   app/views/boards/index.html.erb:34

N+1の解決策

includesメソッドで関連するモデルのデータを先に取得しておく。

@boards = Board.all.includes(:user)
Started GET "/boards" for ::1 at 2020-03-20 17:19:28 +0900
Processing by BoardsController#index as HTML
  Rendering boards/index.html.erb within layouts/application
  Board Load (0.3ms)  SELECT "boards".* FROM "boards"
   app/views/boards/index.html.erb:17
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4], ["id", 5], ["id", 6], ["id", 7], ["id", 8], ["id", 9], ["id", 10]]
   app/views/boards/index.html.erb:17
  Rendered boards/index.html.erb within layouts/application (51.0ms)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 10], ["LIMIT", 1]] (※ログイン中のユーザー)
   app/views/layouts/application.html.erb:13
  Rendered shared/_header.html.erb (2.5ms)
  Rendered shared/_flash_message.html.erb (0.4ms)
  Rendered shared/_footer.html.erb (0.4ms)
Completed 200 OK in 106ms (Views: 102.6ms | ActiveRecord: 0.9ms)

これで、仮にeachメソッドで繰り返し表示させた場合でも、毎回SQLを発行せずに済む。

bulletジェムで、N+1問題を検出

bulletジェムをインストールしておけば、ブラウザ上でn+1問題が発生しているビューを表示したときに、ブラウザにポップアップで警告を出してくれる。

スクリーンショット 2020-04-12 12.05.44.png

開発環境にインストールしておけばよい。

Gemfile
group :development do
  gem 'bullet'
end

ちなみにターミナルのログにも警告を表示してくれて、includesメソッドを使ってくださいね〜って勧めてくれる。
親切だこと。

user: funesakisuke
GET /users/124/bookmarks
USE eager loading detected
  Board => [:user]
  Add to your query: .includes([:user])
Call stack
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?