postgresql - Postgres Slow group by query with max

标签 postgresql group-by

我使用的是 postgres 9.1,我有一个表,其中包含大约 350 万行事件类型 (varchar) 和事件时间 (timestamp) - 以及一些其他字段。只有大约 20 个不同的事件类型,事件时间跨度约为 4 年。

我想获取每个事件类型的最后时间戳。如果我运行如下查询:

select eventtype, max(eventtime)
from allevents
group by eventtype

大约需要 20 秒。选择不同的事件类型同样很慢。查询计划显示了对表的完整顺序扫描 - 不足为奇它很慢。

对上述查询的解释分析给出:

HashAggregate  (cost=84591.47..84591.68 rows=21 width=21) (actual time=20918.131..20918.141 rows=21 loops=1)
  ->  Seq Scan on allevents  (cost=0.00..66117.98 rows=3694698 width=21) (actual time=0.021..4831.793 rows=3694392 loops=1)
Total runtime: 20918.204 ms

如果我添加一个 where 子句来选择特定的事件类型,它需要 40 毫秒到 150 毫秒,这至少是不错的。

选择特定事件类型时的查询计划:

GroupAggregate  (cost=343.87..24942.71 rows=1 width=21) (actual time=98.397..98.397 rows=1 loops=1)
  ->  Bitmap Heap Scan on allevents  (cost=343.87..24871.07 rows=14325 width=21) (actual time=6.820..89.610 rows=19736 loops=1)
        Recheck Cond: ((eventtype)::text = 'TEST_EVENT'::text)
        ->  Bitmap Index Scan on allevents_idx2  (cost=0.00..340.28 rows=14325 width=0) (actual time=6.121..6.121 rows=19736 loops=1)
              Index Cond: ((eventtype)::text = 'TEST_EVENT'::text)
Total runtime: 98.482 ms

主键是(事件类型,事件时间)。我还有以下索引:

allevents_idx (event time desc, eventtype)
allevents_idx2 (eventtype).

如何加快查询速度?

下面@denis 建议的相关子查询的查询播放结果为 14 个手动输入的值:

Function Scan on unnest val  (cost=0.00..185.40 rows=100 width=32) (actual time=0.121..8983.134 rows=14 loops=1)
   SubPlan 2
     ->  Result  (cost=1.83..1.84 rows=1 width=0) (actual time=641.644..641.645 rows=1 loops=14)
          InitPlan 1 (returns $1)
             ->  Limit  (cost=0.00..1.83 rows=1 width=8) (actual time=641.640..641.641 rows=1 loops=14)
                  ->  Index Scan using allevents_idx on allevents  (cost=0.00..322672.36 rows=175938 width=8) (actual time=641.638..641.638 rows=1 loops=14)
                         Index Cond: ((eventtime IS NOT NULL) AND ((eventtype)::text = val.val))
Total runtime: 8983.203 ms

使用@jjanes 建议的递归查询,查询运行时间为 4 到 5 秒,计划如下:

CTE Scan on t  (cost=260.32..448.63 rows=101 width=32) (actual time=0.146..4325.598 rows=22 loops=1)
  CTE t
    ->  Recursive Union  (cost=2.52..260.32 rows=101 width=32) (actual time=0.075..1.449 rows=22 loops=1)
          ->  Result  (cost=2.52..2.53 rows=1 width=0) (actual time=0.074..0.074 rows=1 loops=1)
            InitPlan 1 (returns $1)
                  ->  Limit  (cost=0.00..2.52 rows=1 width=13) (actual time=0.070..0.071 rows=1 loops=1)
                        ->  Index Scan using allevents_idx2 on allevents  (cost=0.00..9315751.37 rows=3696851 width=13) (actual time=0.070..0.070 rows=1 loops=1)
                              Index Cond: ((eventtype)::text IS NOT NULL)
          ->  WorkTable Scan on t  (cost=0.00..25.58 rows=10 width=32) (actual time=0.059..0.060 rows=1 loops=22)
                Filter: (eventtype IS NOT NULL)
                SubPlan 3
                  ->  Result  (cost=2.53..2.54 rows=1 width=0) (actual time=0.059..0.059 rows=1 loops=21)
                        InitPlan 2 (returns $3)
                          ->  Limit  (cost=0.00..2.53 rows=1 width=13) (actual time=0.057..0.057 rows=1 loops=21)
                                ->  Index Scan using allevents_idx2 on allevents  (cost=0.00..3114852.66 rows=1232284 width=13) (actual time=0.055..0.055 rows=1 loops=21)
                                      Index Cond: (((eventtype)::text IS NOT NULL) AND ((eventtype)::text > t.eventtype))
  SubPlan 6
    ->  Result  (cost=1.83..1.84 rows=1 width=0) (actual time=196.549..196.549 rows=1 loops=22)
          InitPlan 5 (returns $6)
            ->  Limit  (cost=0.00..1.83 rows=1 width=8) (actual time=196.546..196.546 rows=1 loops=22)
                  ->  Index Scan using allevents_idx on allevents  (cost=0.00..322946.21 rows=176041 width=8) (actual time=196.544..196.544 rows=1 loops=22)
                        Index Cond: ((eventtime IS NOT NULL) AND ((eventtype)::text = t.eventtype))
Total runtime: 4325.694 ms

最佳答案

您需要的是“跳过扫描”或“loose index scan”。 PostgreSQL 的规划器尚未自动实现这些功能,但您可以通过使用递归查询来诱使它使用这些功能。

WITH RECURSIVE  t AS (
SELECT min(eventtype) AS eventtype FROM allevents
           UNION ALL
SELECT (SELECT min(eventtype) as eventtype FROM allevents WHERE eventtype > t.eventtype)
   FROM t where t.eventtype is not null
)
select eventtype, (select max(eventtime) from allevents where eventtype=t.eventtype) from t;

可能有一种方法可以将 max(eventtime) 折叠到递归查询中,而不是在该查询之外进行,但如果是这样,我还没有想到。

为了提高效率,这需要一个关于 (eventtype, eventtime) 的索引。您可以在事件时间将其设置为 DESC,但这不是必需的。仅当 eventtype 只有几个不同的值(在您的情况下为 21 个)时,这才有效。

关于postgresql - Postgres Slow group by query with max,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19788610/

相关文章:

mysql - 如何计算 group by 子句中的多列和一列中的多个不同值

c# - 使用 join、group-by 和 where 将 sql 转换为 linq

R语言无法使用postgresqlWriteTable在postgresql中插入

ruby - 如何使用 Ruby 处理来自 Postgres 的 "incomplete multibyte character"错误?

c# - max 和 group by 问题与 LINQ

php - 按 id 分组而不是在 php 中分组

mysql - GROUP BY 之前的 LEFT JOIN + ORDER BY

postgresql - 通过F#和Npgsql调用Postgresql中的存储过程

python - PostGIS 空间查询性能低下

sql - 递归选择?