【问题标题】:Rails avoiding N+1 QueriesRails 避免 N+1 查询
【发布时间】:2016-11-18 15:36:11
【问题描述】:

post 有 likers 和 comments 孩子。我想根据它们对帖子进行排序。

class Post < ApplicationRecord
  scope :latest, -> {
    all.sort_by(&:ranking)
  }

  def ranking
    likers.count + comments.count
  end
end

这会调用如下查询:

  Post Load (0.7ms)  SELECT "posts".* FROM "posts"
   (0.4ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 52]]
   (0.4ms)  SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1  [["post_id", 52]]
   (0.2ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 53]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1  [["post_id", 53]]

所以我尝试以下方法:

Post.includes(:comments, :likers).all.sort_by(&:ranking)

这会调用如下查询:

  Post Load (0.7ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71)
  UserPostLike Load (0.3ms)  SELECT "user_post_likes".* FROM "user_post_likes" WHERE "user_post_likes"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71)
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 46
   (0.3ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 52]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1  [["post_id", 52]]
   (0.2ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 53]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1  [["post_id", 53]]

为什么会发生这种情况,我该如何解决?

更新:

我想出了如何解决它,但是一个非常好的解释的答案会很好:

我不得不将count 替换为size。

首字母:

class Post < ApplicationRecord
  scope :latest, -> { 
    all.sort_by(&:ranking)
  }

  def ranking
    likers.count + comments.count
  end
end

之后:

class Post < ApplicationRecord
  ...

  def ranking
    likers.size + comments.size
  end
end

然后,N+1 Query 消失了。我从以下事实中得到提示:当您使用 counter_cache 时,会发生同样的事情。在这种情况下,我没有使用counter_cache,但我仍然必须使用size 而不是count。我假设调用count 会强制Rails 调用COUNT SQL 查询并调用size 使其使用内存中加载的记录。

【问题讨论】:

标签: ruby-on-rails postgresql


【解决方案1】:

你可以像这样使用eager_load:

Post.eager_load(:comments, :likers).sort_by(&:ranking)

渴望加载使用LEFT OUTER JOIN在单个查询中加载所有关联。

Eager Loading Associations

3 ways to do eager loading (preloading) in Rails 3 & 4

【讨论】:

  • 这里的问题不是急切加载,正如您从查询中看到的那样,他的关联已加载。
  • 感谢您的回答,但这并不能解决问题。
  • eager_load 只执行 1 次查询并将所有数据(关联记录)加载到内存中。
  • @MaximusS 不错,我忘记了计数和大小之间的区别,为 ruby​​ 枚举设置大小。
【解决方案2】:

这里的问题有两个方面:

首先,sort_by 立即为我举旗: http://apidock.com/ruby/Array/sort%21

这是一个Array 方法,这意味着您不再构建 ActiveRecord 查询,而是进行数组转换。

由于您包含comments 和likers,因此查询并没有想象中那么糟糕,但这是另一个问题。

.count 的工作方式是预制计数查询SELECT * FROM table。

要获得所需的结果,您需要构建自己的计数和排序查询。

看看这篇文章,希望这能让您更好地了解如何进一步优化: Rails 3 ActiveRecord: Order by count on association

【讨论】:

  • 你发布了一个mysql的例子,在Postgres中你需要聚合所有属性。
  • @Зелёный 聚合所有属性是什么意思?我一直在逃避构建一个查询来计算和排序排名,仅此而已。
【解决方案3】:

在您的情况下,最好的方法是将counter_cache 用于likers 和comments。 更多细节你可以阅读SHORT ARTICLE。这很容易,而且会节省时间和记忆。

如果您使用counter_cache,则不应向您的数据库发出多个请求。现在你的方法是:

def ranking
  likers_count + comments_count
end

另一方面,如果您不想在表中添加列,只需使用includes:

class Post < ApplicationRecord
  scope :latest, -> { 
    includes(:likers, :comments).sort_by(&:ranking)
  }

  def ranking 
    likers.count + comments.count
  end
end

但在这种情况下,您将在每次方法调用时计算 likers 和 comments

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    • 1970-01-01
    • 2019-03-16
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多