10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

N+1問題とは?ふんわり理解から抜け出すために整理

10
Posted at

はじめに

Railsを学習していると、一度は耳にする「N+1問題」。

なんとなく
「クエリがいっぱい発行されてよくないやつ」
という理解はしていましたが、正直なところ仕組みをきちんと説明できませんでした。

そこで今回は、自分の理解を整理するためにも、N+1問題についてまとめてみます。

N+1問題とは

N+1問題とは、
本来最小限のクエリで済むはずのデータ取得が、不要に複数回のSQL発行になってしまう問題です。

例として、UserとPostが1対多の関係にあるとします。

# controller
@users = User.all
<% @users.each do |user| %>
  <%= user.name %>
  <% user.posts.each do |post| %>
    <%= post.title %>
  <% end %>
<% end %>

この場合、発行されるSQLは以下のようになります。

  1. usersを取得するクエリ(1回)
  2. 各userごとにpostsを取得するクエリ(N回)

つまり、
1(users取得) + N(posts取得) = N+1回
ユーザーが100人いれば、SQLは101回発行されます。

データ量が増えるほど、パフォーマンスが悪化していくのが問題です。

  • User.all では users テーブルのデータしか取得していない
  • user.posts を呼び出した時点で、初めて posts テーブルへのクエリが発行される
  • これがループの回数分(ユーザー数)繰り返されるため、N回のクエリになる

どう改善する?

Eager Loading(事前読み込み)

N+1問題が起きるのは Lazy Loading(必要になった時に読み込む) が原因です。
Railsでは includes を使うと Eager Loading(事前にまとめて読み込む) になります。

# N+1が発生する例
@users = User.all

# 改善例
@users = User.includes(:posts)

これにより、Railsは事前にpostsもまとめて取得します。

発行されるSQLは次のようになります。

  1. usersを取得(1回)
  2. 対象usersのpostsをまとめて取得(1回)

つまり、2回で済むようになります。
これを「Eager Loading(事前読み込み)」といいます。

  • N+1問題が起きるのは Lazy Loading(必要になった時に読み込む)が原因
  • includes を使うと Eager Loading(事前にまとめて読み込む)になる

他にもある?

Railsには以下のメソッドもあります。

  • preload
  • eager_load

違いはJOINを使うかどうかなどですが、基本的なN+1対策としては includes を覚えておけば十分だと思いました。

まとめ

N+1問題は、

  • 関連データをループ内で取得することで起きる
  • データ量が増えるほどパフォーマンスが悪化する
  • includes を使うことで解決できる

という問題でした。

「とりあえずincludesをつける」ではなく、

なぜN+1が起きるのか、なぜincludesをつけるのかを理解することが大切
だと感じました。

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?