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

Eva*_*oll 16 postgresql benchmark aggregate array

我刚刚查看了一些为8.4 之前的 PostgreSQL编写的旧代码,我看到了一些非常棒的东西。我记得以前有一个自定义函数可以做一些这样的事情,但我忘记了预先的array_agg()样子。回顾一下,现代聚合是这样写的。

SELECT array_agg(x ORDER BY x DESC) FROM foobar;
Run Code Online (Sandbox Code Playgroud)

然而,曾几何时,它是这样写的,

SELECT ARRAY(SELECT x FROM foobar ORDER BY x DESC);
Run Code Online (Sandbox Code Playgroud)

所以,我用一些测试数据试了一下..

CREATE TEMP TABLE foobar AS
SELECT * FROM generate_series(1,1e7)
  AS t(x);
Run Code Online (Sandbox Code Playgroud)

结果令人惊讶..#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)
Run Code Online (Sandbox Code Playgroud)

那么,这里发生了什么。为什么array_agg是一个内部函数,比规划器的 SQL 巫术慢得多?

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

Erw*_*ter 22

ARRAY 构造函数没有任何“老派”或“过时” (就是这样ARRAY(SELECT x FROM foobar))。它一如既往地现代。将其用于简单的数组聚合。

手册:

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

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

我没有研究源代码,但显然更通用的工具也更昂贵。

一个显着区别:{}如果没有行符合条件,则 ARRAY 构造函数返回一个空数组 ( )。array_agg()返回NULL相同。


pbi*_*len 11

我相信 Erwin 接受的答案可以添加以下内容。

通常,我们使用带有索引的常规表,而不是原始问题中的临时表(没有索引)。需要注意的是ARRAY_AGG在聚合期间完成排序时聚合(例如 )无法利用现有索引

例如,假设以下查询:

SELECT ARRAY(SELECT c FROM t ORDER BY id)
Run Code Online (Sandbox Code Playgroud)

如果我们在 上有一个索引t(id, ...),则可以使用该索引,以支持对 进行顺序扫描,t然后对 进行排序t.id。此外,如果包含在数组中的输出列(此处c)是索引的一部分(例如上的索引t(id, c)或包含的索引t(id) include(c)),则这甚至可能是仅索引扫描。

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

SELECT ARRAY_AGG(c ORDER BY id) FROM t
Run Code Online (Sandbox Code Playgroud)

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

据我所知,这在官方文档中没有记录,但可以从源中获得。这应该适用于所有当前版本,包括 v11。

  • 好点子。但平心而论,使用 `array_agg()` 或类似聚合函数的查询仍然可以利用带有子查询的索引,例如:`SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub`。per-aggregate `ORDER BY` 子句是在您的示例中排除索引使用的原因。当任何一个可以使用相同的索引(或两者都不使用)时,数组构造函数比 `array_agg()` 更快*。它只是不那么通用。参见:https://dba.stackexchange.com/a/213724/3684 (2认同)
  • 是的,这是一个重要的区别。我稍微改变了我的答案,以明确此评论仅在聚合函数必须排序时才成立。在简单的情况下,您确实仍然可以从索引中受益,因为 PostgreSQL 似乎提供了一些保证,即聚合将以与子查询中定义的相同的顺序发生,如链接中所述。这很酷。我想知道这是否仍然适用于分区表和/或 FDW 表和/或并行工作程序 - 以及 PostgreSQL 是否可以在未来的版本中保持这一承诺。 (2认同)
  • 它*是*一个很好的补充。 (2认同)