2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Rails]N+1問題を対処する

Last updated at Posted at 2021-08-02

N+1問題って聞いたことはありますか?

N+1問題はかなり怖い問題で、ぼくはポートフォリオ作成のときに、N+1問題に直面しました。

そのときに、調べた内容をぼくの理解の範囲で、アウトプットしていきます。

#N+1問題とは

そもそもN+1問題とはどんな問題のことをいうのでしょうか?

データベースからデータベースを取り出すときに、大量のSQLが発行されて、動作が重くなる問題のことです。

たとえば、usersテーブルとpostsテーブルで、下記のようなアソシエーションがあったとします。

models/user.rb
class User < ActiveRecord::Base
  has_many :posts
end
models/post.rb
class Post < ActiveRecord::Base
  belongs_to :user
end

で、投稿の一覧を表示していきます。
Postモデルから全データ取得して、それをeach文で回していきます。

controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end
views/posts/index.html.erb
<%= @posts.each do |post| %>
  <%= post.content %>
  <%= post.user.name %>
<% end %>

これで、このビューにアクセスし、ログを確認してみましょう。

User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
:

このようにPostの数だけ、post.userを探すため、SQLが大量に発行されます。

勉強段階の開発環境等では、データ数もそこまで多くないので、そこまで問題になりませんが、実際に運用していくとなると、投稿数も増えていくことが想定されます。

データ数が、1000になると考えると、毎回1000件のデータ全てにSQLを発行していたら、遅くなるのは明白ですね。

なにも対策をとらないとデータ量が増えるにつれて、動作が遅くなる問題が、N+1問題です。

#対策

では、N+1問題がどんなものか分かったところで、対策方法をみていきましょう!

includesメソッドを使っていきます。

includesメソッドは、`アソシエーションの関連付けを事前に取得してくれます。

事前に取得ってよくわかりませんよね。
例をあげて考えてみましょう。

基本構文はこうです。

基本構文
# 関連名はアソシエーションの関連先
モデル名.inculudes(:関連名)
controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    # .allは省略できます
    @posts = Post.includes(:user)
  end
end

これで、ログを見てみましょう!

User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?)  [["id", 1], ["id", 2], ["id", 4]]

このように、post.userに関するSQLは1行で完了しています。

つまりどういうことかというと、
Post.allの場合は、Postのデータを全て取得して、ビューのeach文で表示するときに、postのuser_idとどのユーザーが一致するか全てのユーザーの中から検証していました。

しかし、Post.includes(:user)の場合は、postsテーブルからデータを取得するときに、関連するusersテーブルのデータも取得しているので、each文で表示する時に、その都度SQLを発行する必要はなくなったのです。

#まとめ

N+1問題の対策としては、includesメソッドを使う!

ポートフォリオ作成時に、N+1問題のことはなにも考えずにコーディングしていて、動作確認のときに「あれ?なんか重たいな」となったのが気づいたきっかけでした。

早いうちにN+1問題に触れられてよかったです。

ただ今回は自分なりに調べてみての結論で、他の記事の中では、includesメソッドを使う方法が推奨されていなかったりした記事がありました。

あとしょうがないのかもしれませんが、includesメソッドを使うことで、SQLの発行は改善されたのですが、コードの可読性がかなり下がるなと思いました。

まだまだ理解しきれていないことも多いので、これからもっとデータベースの知識を深めていきたいと思います!

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?