プログラミング学習記録

主にRuby on Railsを使ったプログラミングを学んでいます。

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わかりやすいプログラミング用語サイト

Rails「N+1問題」超分かりやすい解説・解決方法【includesを使おう】 | にょけんのボックス