【问题标题】:Query optimization for index page索引页面的查询优化
【发布时间】:2017-03-02 12:56:57
【问题描述】:

我有一个系统和一个报告模型。系统 has_many 报告和报告属于_to 系统。每个每日报告由每个系统的 175 条记录组成。

我需要在我的 system#index 页面上进行查询,该页面应列出在最近创建报告时过滤的所有系统。这是我的第一次尝试。

@systems = System.joins('LEFT JOIN reports ON reports.system_id = systems.id').group('systems.id').order('MAX(reports.created_at) ASC')

这列出了带有报告(系统负载(2.1 毫秒))但按 system_id 而非报告 created_at 排序的系统。

第二次尝试

@systems = System.joins(:reports).where("reports.created_at = (SELECT MAX(created_at) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.created_at DESC')  

尽管在 report.created_at 上有索引,但这个查询确实很慢(系统负载(546.2 毫秒))。

第三次尝试

@systems = System.joins(:reports).where("reports.id = (SELECT MAX(id) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.id DESC')

也完成了这项工作,比第二次尝试(系统负载(468.3ms))略快,但仍然不够快。

有什么建议吗?

编辑 03032017

我在一个小型测试数据集上做了数字

旧查询

SELECT s.* FROM systems s
JOIN reports r ON r.system_id = s.id
WHERE r.created_at = (
  SELECT MAX(created_at)
  FROM reports p
  group by p.system_id
  having p.system_id = r.system_id)
ORDER BY r.id DESC

Time: 622.683 ms

Philip Couling 解决方案(干净,只返回带有报告的系统)

SELECT systems.*
FROM systems
JOIN (
  SELECT reports.system_id
    , MAX(reports.created_at) created
  FROM reports
  GROUP BY reports.system_id
) AS r_date ON systems.id = r_date.system_id
ORDER BY r_date.created;

Time: 1.434 ms

BookofGreg 解决方案(会给我所有系统,报告或不报告)

select systems.* from systems order by updated_at;

Time: 0.253 ms

我无法使用 systemjack 的解决方案。

最快的解决方案:bookofgreg

最干净的解决方案:philip couling

感谢您的意见。

【问题讨论】:

    标签: ruby-on-rails postgresql activerecord


    【解决方案1】:

    (reports.system_id, reports.created_at) 上的索引可能可以高效地完成这项工作:

    @systems = System.joins(:reports).where("reports.created_at = (SELECT MAX(created_at) FROM reports p where p.system_id = reports.system_id) system_id)").order('reports.created_at DESC') 
    

    另外...

    你的第二段代码:

    System.joins(:reports).where("reports.id = (SELECT MAX(id) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.id DESC')
    

    扩展为:

       SELECT system.*
         JOIN reports ON system.id = reports.system_id
        WHERE reports.created_at = (
                               SELECT MAX(created_at) 
                                 FROM reports p 
                             group by p.system_id 
                               having p.system_id = reports.system_id)
                           )
     ORDER BY reports.id DESC
    

    注意它必须两次查看报告。此外,由于您包含 p.system_id = reports.system_id),因此每个系统记录都会调用一次嵌套查询。

    理想情况下,您希望获取 system_ids 和报告日期的列表: 所以……

        SELECT reports.system_id
             , MAX(reports.created_at) created
          FROM reports
      GROUP BY reports.system_id
    

    然后加入:

      SELECT systems.* 
        FROM systems
        JOIN (
               SELECT reports.system_id
                    , MAX(reports.created_at) created
                 FROM reports
             GROUP BY reports.system_id
             ) AS r_date ON systems.id = r_date.systems_id
    ORDER BY r_date.created
    

    【讨论】:

    • 对这个回复非常满意,感谢您的解释。明天试试这个。
    【解决方案2】:

    如果您不需要页面上的报告数据,一个可能的解决方案是在更新时报告after_save -> { self.system.touch } # in Report。这将导致系统的updated_at 占用报告更新的时间。

    这意味着您可以只按更新时间对系统进行排序,而无需加入。

    此解决方案假定没有其他方法可以更新 System.如果有,那么您可以指定一个时间缓存列,您可以使用它来订购,例如after_save -> { self.system.touch(:report_cached_updated_at) }

    http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-touch

    【讨论】:

    • 非常感谢,这看起来很有希望。我在我的视图中引用了报告数据,但仅引用了最后一个报告日期,在此解决方案中将与 system_updated at 相同。
    • 没问题 :) 上周在 has_many 上为我自己解决了这个问题:通过关系想要显示最近更新的事物的状态。如果它对你有用,记得接受,祝你好运!
    【解决方案3】:

    window function 可能适合您。不确定如何在 Rails 中实现这一点,但获取每个系统最新报告的查询可能如下所示:

    select * from (
        select s.*, r.sytem_id, r.created_at,
            row_number() OVER (PARTITION BY s.id ORDER BY r.created_at desc) AS row
        from systems s
        left join reports r on r.system_id = s.id
    ) where (row = 1 OR r.system_id is null)
    

    检查 null 是因为您的示例中有一个左连接,因此即使没有报告,您也必须需要系统。

    或更简单(但不确定语法是否正确):

    select *
    from systems s
    left join reports r on r.system_id = s.id
    having (r.system_id is null
        OR row_number() OVER (PARTITION BY s.id ORDER BY r.created_at desc) = 1)
    

    【讨论】:

    • 感谢您的意见。从技术角度来看,我认为 Philip Couling 和您的回答是最纯粹的。如果我看性能提升,我倾向于 BookofGreg。每日自动报告导入来自第 3 方 API,由 aproximatley 87000 条记录组成,新系统会自动添加到(静态)系统表中。所以我认为完全避免加入可能最适合我的需求:-)
    猜你喜欢
    • 1970-01-01
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多