N + 1問題
N + 1問題とは
SQLが必要以上に実行されてしまい、パフォーマンスが落ちる問題。
どういう時に起こるかというと、外部キー制約でアソシエーションが定義された複数のテーブルの情報を、横断的に繰り返し処理を用いて取得しようとする場合。例えば、一対多の関係の場合(例ではUserモデルとPostモデル。1人のuserが複数のpostを持つ場合)、
app/models/user.rb
class User < ActiveRecord::Base has_many :posts # 複数形 end
app/models/post.rb
class Post < ActiveRecord::Base belongs_to :user # 単数形 end
Viewでuserのpostのcontent要素を表示したいとき
app/controller/users_controller.rb
class UsersController < ApplicationController def index @users = User.all end end
app/views/posts/index.html.erb
<% @users.each do |user| %> <%= user.post.content %> <% end %>
この時発行されるSQLは、まずControllerのindexアクションでUser.allの実行で一回。Userモデルの情報を全て取得。
その後、Viewのeach文を実行する際、まずuser_idが1のuserのpostの情報を得るためにPostのデータベースにアクセスするSQLが実行され、次の繰り返しでuser_idが2のuserのpostの情報を得るためのSQLが実行され……
と結局、全てのuserに関してpostを得る処理が繰り返しの都度実行される。
つまり、最初にuserの情報を得るために一回(1)
userそれぞれのpostの情報を得るためにuserの人数分の回数(N)
SQLが実行される。扱うデータの数量が大きくなるほど実行しなければならない回数が増えて処理が追いつかなくなってしまうという問題。
対応策
includesメソッドを使う
app/controller/users_controller.rb
class UsersController < ApplicationController def index @users = User.all.incledes(:posts) end end
と変更する。
こうすることで、userの情報とpostの情報をまとめて取得できるので、SQLの実行回数を大幅に減らせる。
参考
Active Record クエリインターフェイス - Railsガイド
【Rails】 N+1問題をincludesメソッドで解決しよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト