web-dev-qa-db-ja.com

array_agg()が非集約ARRAY()コンストラクタよりも遅いのはなぜですか?

8.4より前のPostgreSQL 用に作成された古いコードをレビューしていたところ、本当に気の利いたものを見ました。私はカスタム関数にこれまでその一部を実行させていたのを覚えていますが、pre _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ブードゥーよりもはるかに遅いのはなぜですか?

"PostgreSQL 9.5.5をx86_64-pc-linux-gnuで使用し、gccでコンパイルした(Ubuntu 6.2.0-5ubuntu12)6.2.0 20161005、64 -ビット"

14
Evan Carroll

ARRAYコンストラクター (それがARRAY(SELECT x FROM foobar) is)。相変わらずモダンです。単純な配列集約に使用します。

マニュアル:

サブクエリの結果から配列を作成することもできます。この形式では、配列コンストラクターは、Word ARRAYというキーに続けて、括弧で囲まれた(括弧で囲まれていない)サブクエリで記述されます。

集約関数array_agg() は、より多くの列を含むSELECTリストに統合でき、同じSELECTでより多くの集約を統合できるという点で、より汎用性が高く、GROUP BYを使用して任意のグループを形成できます。 ARRAYコンストラクターは、単一の列を返すSELECTから単一の配列のみを返すことができます。

私はソースコードを研究しませんでしたが、はるかに用途の広いツールもより高価であることは明らかだと思います。

注目すべき違いの1つは、条件を満たす行がない場合、ARRAYコンストラクターは空の配列({})を返します。 array_agg()は、NULLを返します。

17

アーウィンが認めた答えは次のように付け加えることができると思います。

通常、元の質問のような一時テーブル(インデックスなし)の代わりに、インデックス付きの通常のテーブルを使用します。 _ARRAY_AGG_などの集計では、集計中に並べ替えが行われる場合、既存のインデックスを利用できないであることに注意してください。

たとえば、次のクエリを想定します。

_SELECT ARRAY(SELECT c FROM t ORDER BY id)
_

t(id, ...)にインデックスがある場合、tの順次スキャンに続いて_t.id_のソートを優先して、インデックスを使用できます。さらに、配列にラップされている出力列(ここではc)がインデックス(t(id, c)のインデックスやt(id) include(c)のインクルードインデックスなど)の一部である場合)、これはインデックスのみのスキャンである可能性もあります。

次に、そのクエリを次のように書き換えます。

_SELECT ARRAY_AGG(c ORDER BY id) FROM t
_

これで、集計はインデックスを使用せず、メモリ内の行をソートする必要があります(ディスク上の大きなデータセットの場合はさらに悪いことになります)。 これは常にtの順次スキャンであり、その後に集約+ソートが続きます

私の知る限り、これは公式ドキュメントには記載されていませんが、ソースから取得できます。これは、v11を含む現在のすべてのバージョンに当てはまります。

7
pbillen