🖖

Rails N + 1

に公開

場面

親レコード10件に紐づく子レコードA, B, C, Dを取得したい時。

問題のあるコード

親を10件取りに行く(1クエリ)
その後、各親ごとに子A, B, C, Dを取りに行く(10クエリ)

Parent.joins(:children).find_each do |parent|
  parent.children.each do |child|
    puts child.id
  end
end

# *01 Parent Load (2.5ms)  SELECT `parents`.* FROM `parents` INNER JOIN `children` ON `children`.`parent_id` = `parent`.`id` ORDER BY `parent`.`id` ASC LIMIT 1000
# *10 Children Load (2.2ms)  SELECT `children`.* FROM `children` WHERE `children`.`parent_id` = xxx ORDER BY `children`.`id` ASC LIMIT 1000

preload

親を10件取りに行く。(1クエリ)
その後、子A, B, C, Dを10件分まとめて取りに行く(1クエリ)

Parent.preload(:children).each do |parent|
  parent.children.each do |child|
    puts child.id
  end
end
# *01 Parent Load (2.2ms)  SELECT `parents`.* FROM `parents` ORDER BY `parents`.`id` ASC LIMIT 1000...
# *01 SELECT `children`.* FROM `children` WHERE `children`.`parent_id` IN (1, 2, 3, 4, 5)...

eager_load

親に子を連結して、親10件分の子A,B,C,Dを取りに行く。(1クエリ)

Parent.eager_load(:children).each do |parent|
  parent.children.each do |child|
    puts child.id
  end
end
# *01 SQL (4.3ms)  SELECT `parents`.`id` ...

includes

whereやorderを使う場合eager_loadに切り替わる。
whereやorderを使う場合preloadに切り替わる。

Discussion