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


13

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ブードゥー教よりもずっと遅いのはなぜですか?

「gcc(Ubuntu 6.2.0-5ubuntu12)6.2.0 20161005、64ビットでコンパイルされたx86_64-pc-linux-gnuでのPostgreSQL 9.5.5の使用」

回答:


17

ARRAYコンストラクターについて「古い」または「時代遅れ」なものは何もありません(それが何であるかARRAY(SELECT x FROM foobar))。相変わらずモダンです。単純な配列集約に使用します。

マニュアル:

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

集約関数はarray_agg()、はるかにそれが中に統合することができるという点で汎用性がSELECT同じで複数の列、おそらくより多くの集約とリストSELECT、および任意の基を用いて形成することができますGROUP BY。一方、ARRAYコンストラクターSELECT、単一の列を返すことから単一の配列のみを返すことができます。

私はソースコードを勉強しませんでしたが、はるかに汎用性の高いツールがより高価であることは明らかです。


array_aggARRAYコンストラクターがUNION内部的に式としてa とほぼ同等の処理をしているように見える入力の順序を追跡する必要があります。推測array_aggする必要がある場合は、おそらくより多くのメモリが必要になります。これを徹底的にテストすることはできませんでしたが、Ubuntu 16.04で実行されているPostgreSQL 9.6では、ARRAY()クエリORDER BYは外部マージを使用し、array_aggクエリよりも低速でした。あなたが言ったように、コードを読むことを除いて、あなたの答えは私たちが持っている最高の説明です。
ジェフ

@Jeffrey:あなたは、テストケース見つかっarray_agg()ある速い配列コンストラクタよりの?簡単な場合ですか?可能性は非常に低いですが、そうだとすれば、おそらくPostgresがコスト設定の不正確な統計に基づいてクエリプランを決定したためです。array_agg()配列コンストラクターを上回ることは一度もありませんでしたし、何度もテストしました。
アーウィンブランドステッター

1
@ジェフリー:誤解を招くようなキャッシュ効果はありませんか?各クエリを複数回実行しましたか?さらに言うには、テーブル定義、カーディナリティ、および正確なクエリを確認する必要があります。
アーウィンブランドステッター

1
これは本当の答えではありません。多くの汎用ツールは、より具体的なツールと同様に実行できます。汎用性があることが実際にそれを遅くしているのであれば、その汎用性はどうですか?
ギャビンウォール

1
@Jeffrey:Postgresは(コストの見積もりとテーブルの統計に基づいて)バリアントごとに異なるソートアルゴリズムを選択しているようです。そして、ARRAYコンストラクターの劣ったメソッドを選択することになります。これは、推定コストの計算における1つ以上の要因が遠すぎることを示しています。これは一時テーブルにありますか?あなたでしたVACUUM ANALYZEあなたの前には、クエリを実行しますか?検討:dba.stackexchange.com/a/18694/3684
アーウィンブランドステッター

5

Erwinが受け入れた答えは、次のように追加できると思います。

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

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

SELECT ARRAY(SELECT c FROM t ORDER BY id)

インデックスがonのt(id, ...)場合、インデックスを使用して、順次スキャンをオンtにし、その後にソートをオンにすることができt.idます。さらに、配列(ここではc)にラップされている出力列がインデックスの一部(インデックスon t(id, c)またはインクルードインデックスonなど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。集計ごとのORDER BY句は、この例でインデックスの使用を除外するものです。配列コンストラクターは、どちらかが同じインデックスを使用できる(またはどちらも使用できない)場合より高速ですarray_agg()。汎用性はそれほど高くありません。参照:dba.stackexchange.com/a/213724/3684
Erwin Brandstetter

1
そう、それは重要な区別です。この注釈は、集計関数を並べ替える必要がある場合にのみ有効であることを明確にするために、回答を少し変更しました。PostgreSQLは、リンクで説明されているように、サブクエリで定義されたのと同じ順序で集約が行われることを保証しているように見えるため、単純な場合でもインデックスから利益を得ることができます。それはかなりクールです。パーティションテーブルやFDWテーブル、パラレルワーカーの場合、これがまだ当てはまるのかどうか、そして将来のリリースでPostgreSQLがこの約束を守れるかどうかは疑問です。
-pbillen

記録については、受け入れられた答えを疑うつもりは決してありませんでした。集計と組み合わせたインデックスの存在と使用法についての理性への良い追加だと思いました。
pbillen

1
それ良い追加です。
アーウィンブランドステッター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.