2014-10-20 57 views
1

我们有一张表格,其中包含原始分析(如Google Analytics(分析)和类似的数字),用于查看我们的视频。它包含原始视图,下载,加载等数字。每个视频都由video_id标识。为汇总查询避免外部磁盘排序

数据是每天记录的,但是因为我们需要提取大量指标,所以每天可以包含特定video_id的多个记录。例如:

date  | video_id | country | source | downloads | etc... 
---------------------------------------------------------------- 
2014-01-02 |  1 |  us | facebook |  10 | 
2014-01-02 |  1 |  dk | facebook |  13 | 
2014-01-02 |  1 |  dk | admin |  20 | 

我有一个查询,我需要为超过特定日期的新数据的所有视频获取聚合数据。要获得视频ID的我做这个查询:SELECT video_id FROM table WHERE date >= '2014-01-01' GROUP BY photo_id(或者我可以做一个DISTINCT(video_id)没有GROUP BY,性能是相同的)。

一旦我有这些ID,我需要总的聚合数据(所有时间)。结合起来,这变成以下查询:

SELECT 
    video_id, 
    SUM(downloads), 
    SUM(loads), 
    <more SUMs), 
FROM 
    table 
WHERE 
    video_id IN (SELECT video_id FROM table WHERE date >= '2014-01-01' GROUP BY video_id) 
GROUP BY 
    video_id 

有约10列我们SUM(5-10取决于查询)。该EXPLAIN ANALYZE给出如下:

GroupAggregate (cost=2370840.59..2475948.90 rows=42537 width=72) (actual time=153790.362..162668.962 rows=87661 loops=1) 
    -> Sort (cost=2370840.59..2378295.16 rows=2981826 width=72) (actual time=153790.329..155833.770 rows=3285001 loops=1) 
     Sort Key: table.video_id 
     Sort Method: external merge Disk: 263528kB 
     -> Hash Join (cost=57066.94..1683266.53 rows=2981826 width=72) (actual time=740.210..143814.921 rows=3285001 loops=1) 
       Hash Cond: (table.video_id = table.video_id) 
       -> Seq Scan on table (cost=0.00..1550549.52 rows=5963652 width=72) (actual time=1.768..47613.953 rows=5963652 loops=1) 
       -> Hash (cost=56924.17..56924.17 rows=11422 width=8) (actual time=734.881..734.881 rows=87661 loops=1) 
        Buckets: 2048 Batches: 4 (originally 1) Memory Usage: 1025kB 
        -> HashAggregate (cost=56695.73..56809.95 rows=11422 width=8) (actual time=693.769..715.665 rows=87661 loops=1) 
          -> Index Only Scan using table_recent_ids on table (cost=0.00..52692.41 rows=1601328 width=8) (actual time=1.279..314.249 rows=1614339 loops=1) 
           Index Cond: (date >= '2014-01-01'::date) 
           Heap Fetches: 0 
Total runtime: 162693.367 ms 

正如你所看到的,它的使用(相当大的)外部磁盘合并排序并采取了很长一段时间。我不确定为什么这些排序是首先触发的,我正在寻找一种方法来避免它,或者至少将其最小化。我知道增加work_mem可以缓解外部磁盘合并,但在这种情况下,它似乎过度并且work_mem超过500MB似乎是个不好的主意。

该表有两个(相关)索引:一个在video_id上,另一个在(date, video_id)上。

编辑:运行后更新查询ANALYZE table

+0

您的执行计划与您的示例查询不匹配。你有一个合并连接,我无法匹配你向我们显示的SQL。你确定你向我们展示了一切吗? – 2014-10-20 15:17:13

+0

它完全匹配,只有我有变化的是一些名称。我怀疑合并连接是一种优化,其中内部查询被视为JOIN而不是值列表。 – 2014-10-20 15:18:14

+0

连接可能来自postgres将子查询转换为连接。 – 2014-10-20 15:18:42

回答

1

编辑以匹配修改后的查询计划。

由于Postgres需要对结果行进行排序来对它们进行分组,因此您正在进行排序。

该查询看起来好像可以从table(video_id, date)上的索引或table(video_id)上的索引中获益。拥有这样的指数可能会避免排序的需要。

编辑(#2)建议

你也可以考虑测试的替代查询像这样:

SELECT 
    video_id, 
    MAX(date) as latest_date, 
    <SUMs> 
FROM 
    table 
GROUP BY 
    video_id 
HAVING 
    latest_date >= '2014-01-01' 

这避免了连接或子查询,并在table(video_id [, other columns])给出的指标是希望这种排序也可以避免。它计算在整个基表的款项过滤掉你不想组之前,但操作是O(ñ),而排序是O(日志)。因此,如果日期标准不是很有选择性,那么在事后进行检查可能是一种改进。

+0

我应该澄清(将更新该帖子)。该表有'video_id'本身的索引,另一个名为'table_recent_ids'的是(date,video_id)。 – 2014-10-20 15:32:27

+0

尝试在'table(video_id,date)'上添加索引是值得的(在这种情况下,'table(video_id)'上的现有索引将是多余的)。如果postgres使用这样的索引来执行连接,那么它应该认识到它不需要单独的排序。 – 2014-10-20 15:47:46

+0

使用此查询比仅使用(video_id,日期)的原始查询快33%,并且查询计划也非常简单。它仍然在做排序,会看到为其他字段添加索引会做什么。 – 2014-10-21 09:30:12