【问题标题】:sort_by with subquery taking too longsort_by 子查询耗时过长
【发布时间】:2015-04-13 18:58:50
【问题描述】:

我正在尝试优化从此 RoR 代码呈现的查询:

records.sort_by { |r| r.badge_instances.count }.reverse.each do |s|

这会导致 N+1 个查询,因为对于每个 record,都需要有一个 COUNT 用于排序。

澄清一下,badge_instancerecord 的孩子。我可以使用哪些 Rails 方法来呈现一个查询,该查询获取所有徽章实例计数,并保持与每个 record 关联的计数以进行排序?

非常感谢您的帮助!

【问题讨论】:

  • 我忘了提一下,我只是通过在我的 BadgeInstance 表中为外键添加索引来优化这一点,但我确信一旦数据变大就会再次出现问题足够的。谢谢...

标签: ruby-on-rails database postgresql optimization


【解决方案1】:

您可以在获取记录时进行一次查询,然后使用size 而不是count 来防止 N+1 查询:

records = Record.include(:badge_instances).where(...)

records.sort_by { |r| r.badge_instances.size }.reverse.each do |s|
  ...
end

在与@MrYoshiji 交谈时,如果这些表中的任何一个变得非常大,那么这可能会占用大量内存(因为这些对象的所有实例都将被加载到内存中。在这种情况下,替代方案可能是在查询中包含关联记录的计数作为属性:

records = Record.select("records.*, COUNT(badge_instances.id) as badge_instances_count").joins(:badge_instances).group("records.id")

然后,您将可以访问每个 records 上名为 badge_instances_count 的属性,您可以使用该属性按计数大小对记录进行排序。请注意,badge_instances_count 将是一个字符串,因此需要将其转换为整数以便按实际计数排序。

【讨论】:

  • 这有点糟糕:您实际上会将数据库中的每个badge_instance 记录转换为BadgeInstance 的实例(ruby 对象),然后您才会计算数组的大小。在这种情况下,不需要将 badge_instance 记录作为 Ruby 实例化对象,因为我们只想对它们进行计数。
  • 我看不出它与您对 OP 问题的评论有何不同。通过应用程序运行时,它的工作方式与您在评论中建议的相同,因为您的代码与上面的代码基本相同,不是吗?
  • 我没有,在 ActiveRecord::Relation 对象上调用 .size 就像调用数组的 .size。 ActiveRecord::Relation 对象上的.count 实际上会触发 SQL 计数。
  • 正确。由于includes(:badge_instances) 急切地加载了BadgeInstance 对象及其对应的Record,因此调用.size 将计算数组的大小,从而防止N+1 查询。如你所说,如果我使用.count,将会发生N+1 个查询。
  • 这出乎意料地正确,我现在感觉很糟糕:/感谢您的澄清和教训,@CDub!
猜你喜欢
  • 2019-08-02
  • 1970-01-01
  • 2011-11-03
  • 2015-10-23
  • 2012-11-04
  • 2013-07-24
  • 2015-09-08
  • 2016-11-04
相关资源
最近更新 更多