为什么array_agg()比非聚合ARRAY()构造函数慢?


14

我只是在回顾一些为8.4之前的PostgreSQL编写的旧代码,我发现确实很不错。我记得以前有一个自定义函数来执行某些操作,但我忘记了它的外观array_agg()。为了进行回顾,现代聚合是这样写的。

SELECT array_agg(x ORDER BY x DESC) FROM foobar;

但是,从前,它是这样写的,

SELECT ARRAY(SELECT x FROM foobar ORDER BY x DESC);

因此,我尝试了一些测试数据。

CREATE TEMP TABLE foobar AS
SELECT * FROM generate_series(1,1e7)
  AS t(x);

结果令人惊讶。#OldSchoolCool方法大大提高了速度:加快了25%。而且,在使用ORDER的情况下对其进行简化显示出相同的慢度。

# EXPLAIN ANALYZE SELECT ARRAY(SELECT x FROM foobar);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Result  (cost=104425.28..104425.29 rows=1 width=0) (actual time=1665.948..1665.949 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.032..716.793 rows=10000000 loops=1)
 Planning time: 0.068 ms
 Execution time: 1671.482 ms
(5 rows)

test=# EXPLAIN ANALYZE SELECT array_agg(x) FROM foobar;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=119469.60..119469.61 rows=1 width=32) (actual time=2155.154..2155.154 rows=1 loops=1)
   ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.031..717.831 rows=10000000 loops=1)
 Planning time: 0.054 ms
 Execution time: 2174.753 ms
(4 rows)

所以,这是怎么回事。为什么array_agg(一个内部函数)比计划者的SQL伏都教徒慢得多?

使用“ x86_64-pc-linux-gnu上的PostgreSQL 9.5.5,由gcc(Ubuntu 6.2.0-5ubuntu12)6.2.0 20161005,64位编译”

Answers:


17

关于ARRAY构造函数,没有任何“过时的”或“过时的” (就是这样ARRAY(SELECT x FROM foobar))。一如既往的现代化。使用它进行简单的数组聚合。

手册:

也可以根据子查询的结果构造一个数组。在这种形式中,数组构造函数是用关键字编写的,ARRAY后面是带括号的(未括在括号中)子查询。

聚合函数array_agg()是更通用的,因为它可以被集成在一个SELECT与多个列,在相同的可能更多的聚合列表SELECT,并且可以与形成任意组GROUP BY。虽然ARRAY构造函数只能从SELECT返回单个列中返回单个数组。

我没有研究源代码,但显而易见的是,功能更强大的工具也更昂贵。

一个值得注意的区别是:{}如果没有行符合条件,则ARRAY构造函数将返回一个空数组()。array_agg()返回NULL相同。


6

我相信,欧文接受的答案可以加上以下内容。

通常,我们使用带索引的常规表,而不是像原始问题中那样使用临时表(无索引)。值得注意的是,在聚合期间进行排序时,诸如聚合之类的聚合ARRAY_AGG不能利用现有索引

例如,假定以下查询:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

如果我们在上有一个索引t(id, ...),则可以使用该索引,以利于先后依次进行扫描和t排序t.id。此外,如果包装在数组中的输出列(在此处c)是索引的一部分(例如on上的索引t(id, c)或on上的include索引t(id) include(c)),则这甚至可能是仅索引扫描。

现在,让我们将该查询重写如下:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

现在,聚合将不再使用索引,它必须对内存中的行进行排序(对于磁盘上的大型数据集,甚至更糟)。这将始终是顺序扫描,t然后是aggregation + sort

据我所知,这没有在官方文档中记录,但是可以从源头获得。对于所有当前版本(包括v11),情况都是如此。


2
好点子。但公平地说,具有array_agg()或类似聚合函数的查询仍然可以通过子查询来利用索引,例如:SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub。per-aggregate ORDER BY子句在您的示例中排除了索引的使用。数组构造函数比任何一个都可以使用相同的索引(或两者都不使用)时要array_agg()。它只是没有那么多用途。请参阅:dba.stackexchange.com/a/213724/3684
Erwin Brandstetter,

1
是的,这是一个重要的区别。我略微更改了答案,以明确表明此评论仅在聚合函数必须排序时才有效。在简单的情况下,您确实仍然可以从索引中受益,因为PostgreSQL似乎可以保证聚合将按照子查询中定义的相同顺序进行,如链接中所述。太酷了。我想知道在分区表和/或FDW表和/或并行工作器中是否仍然适用-PostgreSQL是否可以在将来的版本中兑现这一承诺。
pbillen

出于记录,我绝不打算怀疑已接受的答案。我只认为这是与索引结合使用索引存在和使用的一个很好的补充。
pbillen

1
一个很好的补充。
Erwin Brandstetter
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.