すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 N+1問題とは?原因と対処法を徹底解説!

ぴっかちゃん
ぴっかちゃん

N+1問題とは、データベースからデータを取り出す際に、大量のSQLが実行されて動作が重くなるという問題です。

N+1問題と対処方法

この章では、N+1問題とその対処方法について解説します。

N+1問題の具体的な内容

railsではallメソッドやfindメソッドを使ってデータベースからデータを取得しています。
ですがターミナルのログを見ると実際には下のようにその都度SQLが実行されています。

SQL

例をあげて考えてみましょう。
ユーザーがツイートを投稿できるアプリがあったとします。
ユーザーの情報はusersテーブルに、ツイートの情報はtweetsテーブルに保存します。
トップページにはツイートの一覧を表示させるので、コントローラーでallメソッドを使い、tweetsテーブルからデータを取得します。
その際、上のように1回SQLが実行されます。

そして、トップページのビューでeachメソッドを使い、一つ一つのツイートを表示させるとします。
この時、usersテーブルとtweetsテーブルでアソシエーションを組んでいて、ツイートした人の名前もトップページで表示するようにします。
ツイートの情報全てはallメソッドで取得してきましたが、ツイートを投稿したユーザーの名前はusersテーブルから取得してこなければなりません。

ですのでeachメソッドで一つのツイートのユーザーの名前を表示させるたびにusersテーブルからレコードを取得するSQLが自動で実行されます。
もしツイートが5個あれば5回SQLが実行されます。
N個あればN回SQLが実行されるので、全てのレコードを取得する1回+N回SQLが実行されるのでN+1問題と呼ばれます。
1+N問題と言った方がわかりやすいかもしれません。

SQLが実行されるときには1回あたりわずかですが時間がかかります。
5回ぐらいの実行であれば動作にそれほど影響はないですが、これが100万回あったときはどうでしょうか?
読み込むまで相当な時間がかかってしまいますよね。
ですのでこの問題が起きないよう気をつける必要があります。

N+1問題の対処法

N+1問題に気付いたときには対処をする必要があります。
その際に使うのがincludesメソッドです。

includesメソッド

includesメソッドは、アソシエーションの関連付けを事前に取得してN +1問題を解決するメソッドです。
includesメソッドに渡す引数は、テーブル名ではなくアソシエーションで定義した関連名を指定します。

includesメソッドの基本構文 -->
1
モデル名.includes(:関連名) # 関連名はテーブル名ではない
includesメソッドの使用例 -->
1
@tweets = Tweet.includes(:user)

上記のように、tweetsテーブルからデータを取得するときに、関連するusersテーブルのデータも取得してくれます。 eachメソッドで一つ一つ表示する際でもすでに表示させるデータを全て取得しているので、その都度SQLを実行する必要は無くなります。

includesメソッドについて詳しくは、N+1問題をincludesメソッドで解決しよう!を参考にしてください。

便利なGemを使ってみよう

複雑なアプリになってくると自分でN+1問題が発生しているか気づかない時があります。
そんなときに便利なGembulletです。
bulletを使うとN+1問題が発生しているビューが表示されたときにポップアップで知らせてくれるので大変便利です。

bulletの使い方

それではbulletをアプリに導入する方法を解説します。
まずはGemfileのdevelopment環境にgemを追加します。

Gemfile -->
1
2
3
group :development do
  gem 'bullet'
end

そしてターミナルでbundle installをします。
次にconfig/environments/development.rbに下記の記述をします。

development.rb -->
1
2
3
4
5
6
7
8
9
10
AppName::Application.configure do
  config.after_initialize do
    Bullet.enable = true
    Bullet.alert = true
    Bullet.bullet_logger = true
    Bullet.console = true
    Bullet.rails_logger = true
    Bullet.add_footer = true
  end
end

これで準備完了です。
実際にn+1問題が発生しているビューを開くと下のようにポップアップウィンドウが開き、教えてくれます。

bullet

とても便利なgemなのでぜひインストールしておきましょう。

ターミナルのログでn+1問題を確認してみよう

それでは実際にどういうSLQが発行されているのか、ターミナルのログを見ると確認することができます。
まずはincludesメソッドを使う前を見てみます。

includeなし

このようにたくさんのSQLが実行されているがわかりますね。
次にincludesメソッドを使った時を見てみましょう。

includeあり

このように明らかにSQLが実行されている回数が違いますよね。N+1問題はアプリのパフォーマンスを低下させる原因となるので注意しましょう。RailsのN+1問題については、こちらの書籍でも触れているのでぜひ参考にしてみてください。

この記事のまとめ

  • n+1問題は、データベースからデータを取り出す際に、大量のSQLが実行されて動作が重くなるという問題のこと
  • アプリのパフォーマンスを低下させる原因となるので注意が必要!
  • アソシエーションの関連付けを事前に取得するincludesメソッドを使って解決しよう!