Post

RailsでのN+1問題の原因と対策

N+1問題の原因

Lazy loading の設計で、テーブル情報が必要になった時だけ、SQLクエリを発行して取得する。

そのせいで、投稿一覧画面を表示するとき、個々の投稿の表示が処理されるたびに、ユーザー情報を取得するSQLクエリが発行されることになる。

n個の投稿があるなら、ユーザー情報を取得するクエリがn回発行される

SELECT "users".* FROM "users" WHERE "users"."id" = ?  x   n回

ただ投稿コレクションを1回のクエリSELECT "boards".* FROM "boards" で一括取得した

これで 1 + N の問題が起こっている。

対策法

Lazy loadingをeager loadingにする 。つまりテーブル情報を事前取得しておくこと。

Railsでは三つのメソッドを提供しているーー

1. includesメソッド利用

IN句で参照する情報を事前取得する -> SELECT "users".* FROM "users" WHERE "users"."id" IN (....)

ただwhere句で関係付けのテーブル情報を絞り込みたい場合は、referencesの指定が必要

Board.all.includes(:user).where('user.name = ?', '太郎').references(:users)

or

Board.all.includes(:user).where(users: {name:'太郎'})

2. preloadメソッド

includesの基本挙動と同じで、関係付けのテーブル情報の絞り込みが不可

3. eager_loadメソッド LEFT JOINを使って1つのクエリだけで関連するレコードをすべて取り出す。

includes.where.referencesの挙動と同じ

Rails 4以前は、includesがよしなにpreloadの方法か、eager_loadの方法か、振り分けてくれるけど、Rails 4以降は基本挙動はpreloadと同じになった。

N+1問題の検出方法

gem 'bullet' N+1問題の発生を監視する

gem 'rack-mini-profiler' SQLクエリ、CPUの消費時間などを計測する。N+1問題の解消前後のパフォーマンス変化が直感できる


参照:

Rails: JOINすべきかどうか、それが問題だ — #includesの振舞いを理解する

The (Silver) Bullet for the N+1 Problem

https://www.mehdi-khalili.com/orm-anti-patterns-part-3-lazy-loading

This post is licensed under CC BY 4.0 by the author.