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

More than 1 year has passed since last update.

N+1問題とは

N+1問題とは必要以上にクエリを発行することによりパフォーマンスを低下させてしまうことです。

例えばUser : Post = 1 : 多の場合を考えます。
Railsで実行すると以下のようになります。

    [16] pry(main)> users = User.all
         User Load (1.3ms)  SELECT "users".* FROM "users"
    [17] pry(main)> users.each do |user|
    [17] pry(main)*   puts user.posts
    [17] pry(main)* end  
    #<Post:0x0000557716805150>
    #<Post:0x0000557716804fe8>
      Post Load (1.8ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 2]]
      Post Load (1.0ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 4]]
    #<Post:0x00005577167c8868>
      Post Load (1.6ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 5]]
      Post Load (1.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 6]]

Userに結びつくPostが都度フェッチされていることがわかります。

N+1問題の解消

Railsでの解決方法

Railsでは、includesメソッドを使えば解消することができます。

    [19] pry(main)> users = User.all.includes(:posts)
    =>   User Load (1.2ms)  SELECT "users".* FROM "users"
      Post Load (9.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN ($1, $2, $3, $4, $5)  [["user_id", 1], ["user_id", 2], ["user_id", 4], ["user_id", 5], ["user_id", 6]]
    [20] pry(main)> users.each do |user|
    [20] pry(main)*   puts user.posts
    [20] pry(main)* end  
    #<Post:0x00005577165626c8>
    #<Post:0x0000557716562560>
    #<Post:0x0000557716562830>

これで解消はするのですが、実際にこれは何をしているのでしょうか?
RailsでのN+1問題の解消方法は分かりましたが、N+1問題自体の解消方法は理解できていないように思います。

リレーションを呼ぶメソッドの定義箇所
N+1問題が起きている場合でも起きていない場合でもuser.postsのようにリレーションを使って呼び出しているので、それが定義されている場所を見ていきます。

    [1] pry(main)> users = User.all
    =>   User Load (4.8ms)  SELECT "users".* FROM "users"
    [2] pry(main)> users[0].posts.loaded?
    => false
  • includesする場合
    [1] pry(main)> users = User.all.includes(:posts)
    =>   User Load (2.3ms)  SELECT "users".* FROM "users"
      Post Load (6.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN ($1, $2, $3, $4, $5)  [["user_id", 1], ["user_id", 2], ["user_id", 4], ["user_id", 5], ["user_id", 6]]
    [2] pry(main)> users[0].posts.loaded?
    => true

結論

先に1回クエリを発行し、キャッシュをすることによってN+1問題を解消している。
それ以外にもSQLのJOINを使っても解消できるようなので、試しにやってみたいと思います。

終わりに

ActiveRecordに頼りすぎていてSQLの勉強をまともにしていなかったので、そのつけが今来ているように感じてます。
Railsガイド推奨のSQL tutorialを試しにやってみようと思います。

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