この記事では、N+1問題の正しい理解と、解決するためのメソッドを紹介します。
第1回では、N+1問題とはなにか、そしてどのような状態で発生するのかを具体例とともに解説します。
この記事を読むことで、N+1問題の正しい理解、ケースに応じたN+1問題を解決するメソッドの使い分けができるようになるはずです。
##本記事の内容
- N+1問題とは
- N+1問題が発生する状況
- SQLが発行されるタイミング
##開発環境
- Ruby 2.7.2
- rails 6.1.0
##アソシエーション
users
テーブルとposts
テーブルが1対多の関係でアソシエーションされています。
ユーザー1人に対して、投稿を複数持っている基本的な構成です。
それではさっそくいきましょう。
##N+1問題とは
N+1問題とは、1つのテーブルへのSQL発行と、複数回(N回)の関連テーブルへのSQL発行がおこなわれてしまうことです。
わかりやすくいうと、eachメソッド
などのループ処理のときに、大量のSQLが発行されてしまうことです。
具体例をみていきましょう。
##N+1問題が発生する状況
User.limit(5).each do |user|
p user.posts
end
SQLを確認してみましょう。
User Load (1.7ms) SELECT "users".* FROM "users" LIMIT $1 [["LIMIT", 5]]
Post Load (1.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (1.4ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (2.8ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (2.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (1.7ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
users
テーブルに対して1回SELECT句を発行し、それぞれのユーザーに紐づく投稿をposts
テーブルから5回取得しています。N+1問題とは、はじめの1回で軸となるモデル(テーブル)を取得し、そのモデルに対するデータ数分(N回)のSQLが実行されてしまう状態のことです。
今回はlimit
メソッドでデータ取得数を制限しましたが、N+1問題は、そのデータ量に比例してパフォーマンスが劣化してしまいます。初学者のポートフォリオレベルでは気にならないかもしれませんが、実務では何万、何十万とデータを扱うので、決して無視できません。
現状データ量が少ないとしても、サービスの成長とともにパフォーマンスの劣化は避けられないです。
また、大量のSQLを発行することはDBに高負荷を与えることにもつながり別な問題を発生させる原因にもなります。
とくに1対多のアソシエーションで発生することが多いです。
##SQLが発行されるタイミング
RailsでSQLが発行されるタイミングは、SQLの実行結果をアプリケーションで使用するときです。
User.limit(5).each do |user|
p user.posts #=>このタイミングでSQLが発行される
end
どのタイミングでSQLが発行されるのか理解することで、パフォーマンスを意識した開発が可能になります。
ぜひ自分のポートフォリオを見直してみて、改善できそうなところを見つけてみてください。
N+1問題を解決するメソッドについては、次回まとめていきます。
##まとめ
- N+1問題とは、1つのテーブルへのSQL発行と、複数回(N回)の関連テーブルへのSQL発行がおこなわれてしまうこと。
- とくに1対多のループ処理で発生しやすい。
- RailsでSQLが発行されるタイミングは、SQLの実行結果をアプリケーションで使用するとき。