我有一张约有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
我是整个查询优化业务的新手,我不知道接下来该做什么。有什么线索可以让我的查询运行得更快?
- 编辑 -
看起来我正在达到指数的极限。这几乎是在这个表上运行的唯一查询(尽管日期的值发生了变化)。有没有办法对表格进行分区?或者创建一个包含所有计数值的缓存表?还是其他任何选择?
答案 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列进行切片和切块。