今回はアプリケーションのパフォーマンス低下に繋がる問題「N+1問題」について解説したいと思います。Railsを学習したての頃はなかなか理解しにくい内容かとは思いますが、開発する上でパフォーマンスは非常に重要になってくるので、ぜひ理解しておきましょう。
N+1問題とは
N+1問題とは簡単に言うと、データベースへのアクセス回数が余計に多くなってしまう現象です。加えてこれは、モデル間のアソシエーションを利用する場合に発生します。
もう少し具体的に見ていきましょう。
例えばusersテーブルとpostsテーブルがあり、それらが1対多の関係で結びついているとします。(下図)

そしてpostsテーブルからレコードを全て取得する記述をしたとします。(以下コード)
@posts = Post.all
そうすると毎回user_idを変更してusersテーブルからレコードを取得してくるため、処理スピードが遅くなってしまいます。(下図)
これがN+1問題です。

# usersテーブルから取得(1回目)
SELECT `users`.* FROM `users` WHERE `users`.id = 1 LIMIT 1
# usersテーブルから取得(2回目)
SELECT `users`.* FROM `users` WHERE `users`.id = 2 LIMIT 1
# usersテーブルから取得(3回目)
SELECT `users`.* FROM `users` WHERE `users`.id = 3 LIMIT 1
解決方法
このN+1問題を解決するには、includesメソッドを使用します。includesメソッドを使用することにより、必要なレコードをまとめて取得することができます。(下図)

# usersテーブルから取得(1回のみ)
SELECT `users`.* FROM `users` WHERE `users`.`id` IN(1, 2, 3)
記述方法としては以下になります。
@post = Post.includes(:user)
includesメソッドの記述方法はこのようになります。「モデル名」の先頭は大文字、「紐付くモデル名」は全て小文字で記述する点に注意しましょう。
モデル名.includes(:紐付くモデル名)
RailsからDBにアクセスする際に起きていること
ここからは余談にはなりますが、知っておいて損はないので紹介したいと思います。
みなさんもご存知の通り、Railsでは例えば以下のように記載するだけでデータベースに対して命令できますよね。
@posts = Post.all
しかし実際は、MySQLがこの命令をSQL(データベースを操作する言語)に変換しています。もし変換しない場合だと以下のようになります。
SELECT
*
FROM
posts
この文はターミナルでよく目にすると思います。この文をさらに噛み砕くと「*」は「レコード全て」、「posts」は「postsテーブルから」という意味になります。(下記コード)
SELECT
* (レコード全部とってきて)
FROM
posts (postsテーブルから)
データベースとのやりとりはRubyで行なっていないので、ぜひ覚えておいてくださいね。
まとめ
- N+1問題とは、データベースへのアクセス回数が余計に多くなってしまう現象のこと
- N+1問題を解決するには、includesメソッドを使用する
- Ruby文でデータベースを操作できることをO/Rマッピングという
今回はアプリケーションのパフォーマンス低下に繋がる問題「N+1問題」について解説しました。練習用の小さなアプリケーションではあまり気にする必要はありませんが、本格的にアプリケーションを開発する際は、パフォーマンスは非常に重要になってきますので、ぜひincludesメソッドを使いこなせるように練習しておきましょう。