在postgres的一张大桌上按日期查询加速一组

时间:2011-01-13 00:33:15

标签: sql database postgresql indexing

我有一张约有2000万行的表格。为了论证,我们可以说表中有两列 - 一个id和一个时间戳。我想要计算每天的物品数量。这就是我现在所拥有的。

  SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

没有任何索引,这需要大约30秒才能在我的机器上运行。这是解释分析输出:

 GroupAggregate  (cost=675462.78..676813.42 rows=46532 width=8) (actual time=24467.404..32417.643 rows=346 loops=1)
   ->  Sort  (cost=675462.78..675680.34 rows=87021 width=8) (actual time=24466.730..29071.438 rows=17321121 loops=1)
         Sort Key: (date("timestamp"))
         Sort Method:  external merge  Disk: 372496kB
         ->  Seq Scan on actions  (cost=0.00..667133.11 rows=87021 width=8) (actual time=1.981..12368.186 rows=17321121 loops=1)
               Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 32447.762 ms

由于我看到了顺序扫描,我试图在日期聚合上进行索引

CREATE INDEX ON actions (DATE(timestamp));

将速度降低约50%。

 HashAggregate  (cost=796710.64..796716.19 rows=370 width=8) (actual time=17038.503..17038.590 rows=346 loops=1)
   ->  Seq Scan on actions  (cost=0.00..710202.27 rows=17301674 width=8) (actual time=1.745..12080.877 rows=17321121 loops=1)
         Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 17038.663 ms

我是整个查询优化业务的新手,我不知道接下来该做什么。有什么线索可以让我的查询运行得更快?

- 编辑 -

看起来我正在达到指数的极限。这几乎是在这个表上运行的唯一查询(尽管日期的值发生了变化)。有没有办法对表格进行分区?或者创建一个包含所有计数值的缓存表?还是其他任何选择?

6 个答案:

答案 0 :(得分:6)

  

有没有办法对表格进行分区?

是:
http://www.postgresql.org/docs/current/static/ddl-partitioning.html

  

或者创建一个包含所有计数值的缓存表?还是其他任何选择?

当然可以创建“缓存”表。但这取决于您需要多长时间的结果以及需要的准确程度。

CREATE TABLE action_report
AS
SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

然后SELECT * FROM action_report会及时为您提供所需内容。然后,您将安排一个cron作业,以定期重新创建该表。

如果时间范围随每个查询而变化,或者该查询每天只运行一次,那么这种方法当然无济于事。

答案 1 :(得分:2)

通常,如果返回的预期行数很高,大多数数据库都会忽略索引。这是因为对于每个索引命中,它还需要找到该行,因此执行全表扫描更快。这个数字在10,000到100,000之间。您可以通过缩小日期范围并使用索引查看postgres翻转到的位置来试验这一点。在这种情况下,postgres计划扫描17,301,674行,因此您的表非常大。如果你把它做得很小而且你仍觉得postgres做出了错误的选择,那么试着在桌面上运行一个分析,以便postgres得到它的近似值。

答案 2 :(得分:1)

看起来该范围几乎涵盖了所有可用数据。

这可能是一个设计问题。如果您经常运行此操作,最好创建一个仅包含日期的列timestamp_date。然后在该列上创建索引,并相应地更改查询。该列应由insert + update触发器维护。

SELECT timestamp_date AS day, COUNT(*)
FROM actions
WHERE timestamp_date >= '20100101'
  AND timestamp_date <  '20110101'
GROUP BY day;

如果对于日期范围将找到的行数(并且它只是一小部分)我错了,那么您可以仅对timestamp列本身尝试索引,将WHERE子句应用于该列(鉴于范围也适用)

SELECT DATE(timestamp) AS day, COUNT(*)
FROM actions
WHERE timestamp >= '20100101'
  AND timestamp <  '20110101'
GROUP BY day;

答案 3 :(得分:0)

尝试运行explain analyze verbose ...以查看聚合是否正在使用临时文件。也许增加work_mem以允许在内存中完成更多工作?

答案 4 :(得分:0)

work_mem设置为2GB并查看是否更改了计划。如果没有,你可能没有选择。

答案 5 :(得分:0)

您对此类DSS类型查询的真正需求是描述天数的日期表。在数据库设计术语中,它被称为日期维度。要填充此类表,您可以使用我在本文中发布的代码:http://www.mockbites.com/articles/tech/data_mart_temporal

然后在actions表的每一行中输入相应的date_key。

您的查询将变为:

SELECT
   d.full_date, COUNT(*)
FROM actions a 
JOIN date_dimension d 
    ON a.date_key = d.date_key
WHERE d.full_date = '2010/01/01'
GROUP BY d.full_date

假设键和full_date上的索引,这将是超快的,因为它在INT4键上运行!

另一个好处是你现在可以通过任何其他date_dimension列进行切片和切块。