☝️

【Rails】N+1問題とincludesメソッド

2023/07/06に公開

N+1問題とは

N+1問題は、データベースのクエリ実行回数(取得、更新、削除など)が増えることでパフォーマンスの低下を引き起こすことです。

具体例を挙げて説明します。

例えば、favorite(いいね)モデルとbookモデルが関連しているとします。

model
class Book < ApplicationRecord
  has_many :favorites
end

class Favorite < ApplicationRecord
  belongs_to :book
end

そして、次のコードでbook情報を取得する場合を考えます。

@books = Book.all

この場合、bookの情報を取得するために次のようなクエリがデータベースに発行されます。

sql
SELECT * FROM books

ここで、@books に含まれる各bookに対していいね情報を取得したい場合、次のようにループでアクセスすると、bookごとに個別のクエリが発行されることになります(N+1問題)。

@books = Book.all

@books.each do |book|
  puts "#{book.title} - #{book.favorites.count}"
end

この場合、まずBook.allでbookの一覧を取得するために1つのクエリが発生します。その後、ループ内で各bookのいいね数を取得するために、book.favorites.countが実行されます。このループ処理により、bookの数だけ追加のクエリが発生し、N+1問題が発生します。つまり、bookの数が増えると、必要なクエリの数も増え、パフォーマンスの低下や遅延が発生する可能性があります。

N+1問題を回避するためには、Eager Loading(事前読み込み)を使用します。具体的には、関連するデータを1度のクエリでまとめて取得することで、N+1問題を回避します。これを解決するためにはincludesメソッドを使います。

includesメソッド

includes を使用すると、関連するいいね情報を一度のクエリで取得することができます。具体的には次のように書きます。

@books = Book.includes(:favorites)

この場合、book情報と関連するいいね情報が同時に取得されます。つまり、次のようなクエリがデータベースに発行されます。

sql
SELECT * FROM books
SELECT * FROM favorites WHERE book_id IN (...)

この結果、@books に含まれる書籍のいいね情報にアクセスする際には、追加のクエリが発行されず、既に取得された情報が使用されます。これにより、N+1問題が回避され、パフォーマンスが向上します。

つまり、includes を使用することで、関連するデータを一括で取得することができます。これにより、データベースへのクエリ回数が減り、処理速度が向上します。

Discussion