在 PostgreSQL 中优化窗口函数以使用索引

Posted

技术标签:

【中文标题】在 PostgreSQL 中优化窗口函数以使用索引【英文标题】:Optimizing window function in PostgreSQL to use index 【发布时间】:2016-01-12 02:52:36 【问题描述】:

我在 PostgreSQL 9.2 数据库中有一个表,创建和填充如下:

CREATE TABLE foo( id integer, date date );

INSERT INTO foo
SELECT (id % 10) + 1, now() - (id % 50) * interval '1 day'
FROM generate_series(1, 100000) AS id;

现在,我需要找到所有对 (id, date),使得日期是具有相同 id 的所有对中的最大值。该查询是众所周知的,通常使用名为ROW_NUMBER()的窗口函数

SELECT id, date
FROM (
    SELECT id, date, ROW_NUMBER() OVER (PARTITION BY id ORDER BY date DESC) rn
    FROM foo
) sbt
WHERE sbt.rn = 1;

现在,我询问该查询的计划,并发现WindowAgg 节点需要首先对表进行排序。

Subquery Scan on sbt  (cost=11116.32..14366.32 rows=500 width=8) (actual time=71.650..127.809 rows=10 loops=1)
  Filter: (sbt.rn = 1)
  Rows Removed by Filter: 99990
    ->  WindowAgg  (cost=11116.32..13116.32 rows=100000 width=8) (actual time=71.644..122.476 rows=100000 loops=1)
          ->  Sort  (cost=11116.32..11366.32 rows=100000 width=8) (actual time=71.637..92.081 rows=100000 loops=1)
                Sort Key: foo.id, foo.date
                Sort Method: external merge  Disk: 1752kB
                ->  Seq Scan on foo  (cost=0.00..1443.00 rows=100000 width=8) (actual time=0.006..6.138 rows=100000 loops=1)

正如预期的那样,排序占用了大部分查询执行时间,使用indexes 肯定会有所帮助。

所以我创建了CREATE INDEX ON foo(id, date) 并希望它现在可以使用索引。但事实并非如此。我对external merge 有相同的计划,甚至关闭sequential scan 也没有用。我刚刚结束了Bitmap Index Scan

->  Sort  (cost=12745.58..12995.58 rows=100000 width=8) (actual time=69.247..90.003 rows=100000 loops=1)
      Sort Key: foo.id, foo.date
      Sort Method: external merge  Disk: 1752kB
      ->  Bitmap Heap Scan on foo  (cost=1629.26..3072.26 rows=100000 width=8) (actual time=5.359..12.639 rows=100000 loops=1)
            ->  Bitmap Index Scan on foo_id_date_idx  (cost=0.00..1604.26 rows=100000 width=0) (actual time=5.299..5.299 rows=100000 loops=1)

问题WindowAgg 可以使用索引进行排序吗?我认为它不能但不明白为什么...GroupAggreagtes 可以,并且它可以提高性能。

【问题讨论】:

您的排序发生在磁盘上。尝试为您的会话增加work_mem,看看是否会有所改善 @a_horse_with_no_name 是的,我知道改进work_mem 会将排序算法切换到quick sort,如果内存足够的话。但是为什么我们不能使用foo(id, date) 上的索引提供的排序以及窗口函数呢?我们可以使用聚合的。 【参考方案1】:

要匹配您创建的索引:

CREATE INDEX ON foo(id, date)

你必须这样做:

ROW_NUMBER() OVER (PARTITION BY id ORDER BY date DESC <b>NULLS LAST</b>) 

这是ASC的完美逆序。

除此之外,您可以运行:

SELECT DISTINCT ON (id)
       id, date
FROM   foo
ORDER  BY id, date DESC NULLS LAST;

但这可能不是你想问的。无论哪种方式,我都会制作索引:

CREATE INDEX ON foo(id, date DESC NULLS LAST)

所以max(date) 是每个id 的第一个索引条目。 相关:

PostgreSQL sort by datetime asc, null first? Why do NULL values come first when ordering DESC in a PostgreSQL query? Select first row in each GROUP BY group? Optimize groupwise maximum query

【讨论】:

不幸的是,它对我不起作用。你确定还是我做错了什么……? 我的意思是(PARTITION BY id ORDER BY date ASC NULLS LAST) 太棒了,你只是个巫师!!!我将索引重新创建为CREATE INDEX ON foo(id, date DESC NULLS LAST),它工作得很好(性能提高了两倍)。但是您不能添加一点解释或一些参考资料来详细了解为什么像我一样创建索引不适用于窗口聚合。 @St.Antario:我添加了一些相关答案以及更多详细信息和链接。【参考方案2】:

您可以将逻辑“日期是所有具有相同id的对中最大的一个”重写为直接翻译:

SELECT id, date
FROM (
    SELECT id, date, MAX(date) OVER (PARTITION BY id) as maxDate
    FROM foo
) sbt
WHERE date = maxDate;

它与ROW_NUMBER 不完全相同,而是RANK,它可能返回具有相同date 的多行

【讨论】:

以上是关于在 PostgreSQL 中优化窗口函数以使用索引的主要内容,如果未能解决你的问题,请参考以下文章

为大型 Postgresql 表优化嵌套连接窗口函数

Postgresql 建索引性能优化

PostgreSQL 分析函数窗口化以查找列中的下一个值

掌握查询利器 深入理解PostgreSQL索引原理与优化

如何优化在 postgresql 中查询这些数据?

PostgreSQL 性能优化 短查询 覆盖索引,前缀索引,索引和排序