【问题标题】:Avoiding external disk sort for aggregate query避免对聚合查询进行外部磁盘排序
【发布时间】:2014-10-20 15:11:30
【问题描述】:

我们有一个表格,其中包含原始分析数据(如 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(或者,我可以在没有 GROUP BY 的情况下执行 DISTINCT(video_id),性能相同)。

拥有这些 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 列求和(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 后更新查询。

【问题讨论】:

  • 您的执行计划与您的示例查询不匹配。您在那里有一个合并连接,我无法与您向我们展示的 SQL 匹配。你确定你向我们展示了一切吗?
  • 完全匹配,我唯一改变的是一些名字。我怀疑合并连接是一种优化,其中内部查询被视为 JOIN 而不是值列表。
  • 连接可能是 postgres 将子选择转换为连接的结果。
  • 子查询中的group by 没用。摆脱 id,它不是必需的。另外,似乎根本不需要子选择,因为它从与外部查询相同的表中进行选择。这可以用一个简单的where date &gt;= '2014-01-01' 条件替换
  • a_horse_with_no_name:不,它不能。我想要在特定日期之后有数据的视频,但我想要这些视频的聚合数据所有时间(不仅仅是在给定日期之后)。因此,它不能合并。

标签: sql postgresql aggregate


【解决方案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(n),而排序是 O(m 记录 m)。因此,如果日期标准不是很有选择性,那么事后检查可能会有所改进。

【讨论】:

  • 我可能应该澄清一下(将更新帖子)。该表在video_id 本身和另一个名为table_recent_ids 上的索引是(日期,video_id)。
  • 值得尝试在table(video_id, date) 上添加索引(在这种情况下,table(video_id) 上的现有索引将是多余的)。如果 postgres 使用这样的索引来执行连接,那么它应该认识到它不需要单独的排序。
  • 使用此查询比仅使用 (video_id, date) 的原始查询快 33%,并且查询计划也简单得多。它仍在进行排序,将查看为其余字段添加索引会做什么。
猜你喜欢
  • 1970-01-01
  • 2023-03-08
  • 2023-03-02
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-06
相关资源
最近更新 更多