it-swarm.com.de

Warum ist array_agg () langsamer als der nicht aggregierte ARRAY () -Konstruktor?

Ich habe gerade einen alten Code überprüft, der für vor 8.4 PostgreSQL geschrieben wurde, und ich habe etwas wirklich Schickes gesehen. Ich erinnere mich, dass eine benutzerdefinierte Funktion früher etwas davon erledigt hat, aber ich habe vergessen, wie pre -array_agg() aussah. Zur Überprüfung wird die moderne Aggregation so geschrieben.

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

Es war jedoch einmal so geschrieben,

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

Also habe ich es mit einigen Testdaten versucht.

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

Die Ergebnisse waren überraschend. Der # OldSchoolCool-Weg war enorm schneller: eine 25% ige Beschleunigung. Darüber hinaus zeigte die Vereinfachung ohne die BESTELLUNG die gleiche Langsamkeit.

# 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)

Also, was ist hier los? Warum ist array_agg, eine interne Funktion, die so viel langsamer ist als das SQL-Voodoo des Planers?

Verwenden von " PostgreSQL 9.5.5 unter x86_64-pc-linux-gnu, kompiliert von gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64 -bisschen"

14
Evan Carroll

Es gibt nichts "altes" oder "veraltetes" an einem ARRAY-Konstruktor ( Das ist ARRAY(SELECT x FROM foobar)). Es ist modern wie immer. Verwenden Sie es für die einfache Array-Aggregation.

Das Handbuch :

Es ist auch möglich, ein Array aus den Ergebnissen einer Unterabfrage zu erstellen. In dieser Form wird der Array-Konstruktor mit dem Schlüsselwort ARRAY gefolgt von einer Unterabfrage in Klammern (nicht in Klammern) geschrieben.

Die Aggregatfunktion array_agg() ist vielseitiger, da sie in eine SELECT -Liste mit mehr Spalten, möglicherweise mehr Aggregationen in derselben SELECT integriert werden kann ] und beliebige Gruppen können mit _GROUP BY_ gebildet werden. Während ein ARRAY-Konstruktor nur ein einzelnes Array von einem SELECT zurückgeben kann, das eine einzelne Spalte zurückgibt.

Ich habe den Quellcode nicht studiert, aber es scheint offensichtlich, dass ein viel vielseitigeres Tool auch teurer ist.

Ein bemerkenswerter Unterschied: Der ARRAY-Konstruktor gibt ein leeres Array (_{}_) zurück, wenn keine Zeilen qualifiziert sind. array_agg() gibt NULL für dasselbe zurück.

17

Ich glaube, die akzeptierte Antwort von Erwin könnte wie folgt hinzugefügt werden.

Normalerweise arbeiten wir mit regulären Tabellen mit Indizes anstelle von temporären Tabellen (ohne Indizes) wie in der ursprünglichen Frage. Es ist nützlich zu beachten, dass Aggregationen wie ARRAY_AGG Vorhandene Indizes nicht nutzen können, wenn die Sortierung während der Aggregation erfolgt.

Nehmen Sie beispielsweise die folgende Abfrage an:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Wenn wir einen Index für t(id, ...) haben, könnte der Index zugunsten eines sequentiellen Scans für t verwendet werden, gefolgt von einer Sortierung für t.id. Wenn die Ausgabespalte, die in das Array eingeschlossen wird (hier c), Teil des Index ist (z. B. ein Index für t(id, c) oder ein Include-Index für t(id) include(c) ), dies könnte sogar ein Nur-Index-Scan sein.

Schreiben wir diese Abfrage nun wie folgt um:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Jetzt verwendet die Aggregation den Index nicht mehr und muss die Zeilen im Speicher sortieren (oder noch schlimmer bei großen Datenmengen auf der Festplatte). Dies ist immer ein sequentieller Scan von t, gefolgt von Aggregation + Sortierung.

Soweit ich weiß, ist dies nicht in der offiziellen Dokumentation dokumentiert, sondern kann aus der Quelle abgeleitet werden. Dies sollte für alle aktuellen Versionen, einschließlich Version 11, der Fall sein.

7
pbillen