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

13

Ich habe gerade einen alten Code überprüft, der für PostgreSQL vor 8.4 geschrieben wurde , und ich habe etwas wirklich Gutes gesehen. Ich erinnere mich, dass früher eine benutzerdefinierte Funktion etwas davon erledigte, aber ich vergaß, wie es vorher array_agg()ausgesehen hatte. Zur Überprüfung wird die moderne Aggregation folgendermaßen 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 ein paar Testdaten versucht ..

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

Die Ergebnisse waren überraschend. Die #OldSchoolCool-Methode 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, so viel langsamer als der 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-bit"

Evan Carroll
quelle

Antworten:

17

Es gibt nichts " Altmodisches " oder "Veraltetes" an einem ARRAY-Konstruktor (das ist was ARRAY(SELECT x FROM foobar)ist). 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 ARRAYgefolgt von einer in Klammern gesetzten (nicht in Klammern gesetzten) Unterabfrage geschrieben.

Die Aggregatfunktionarray_agg() ist viel vielseitiger, da sie in eine SELECTListe mit mehr Spalten, möglicherweise mehr Aggregationen in derselben SELECT, integriert und mit beliebigen Gruppen gebildet werden kann GROUP BY. Während ein ARRAY-Konstruktor nur ein einzelnes Array aus SELECTeiner einzelnen Spalte zurückgeben kann.

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

Erwin Brandstetter
quelle
array_aggmuss die Reihenfolge seiner Eingaben verfolgen, in der der ARRAYKonstruktor UNIONintern etwas zu tun scheint, das ungefähr dem Ausdruck a entspricht. Wenn ich eine Vermutung wagen array_aggmüsste , würde wahrscheinlich mehr Speicher benötigt. Ich konnte dies nicht ausführlich testen, aber auf PostgreSQL 9.6 unter Ubuntu 16.04 verwendete die ARRAY()Abfrage ORDER BYeine externe Zusammenführung und war langsamer als die array_aggAbfrage. Wie Sie sagten, ist Ihre Antwort nach dem Lesen des Codes die beste Erklärung, die wir haben.
Jeffrey
@Jeffrey: Du hast einen Testfall gefunden, bei dem array_agg()es schneller geht als beim Array-Konstruktor? Für einen einfachen Fall? Sehr unwahrscheinlich, aber wenn ja, wahrscheinlich, weil Postgres seine Entscheidung für einen Abfrageplan auf einer ungenauen Statistik der Kosteneinstellungen beruhte. Ich habe noch nie gesehen array_agg(), einen Array-Konstruktor zu übertreffen, und ich habe viele Male getestet.
Erwin Brandstetter
1
@ Jeffrey: Keine irreführenden Caching-Effekte? Haben Sie jede Abfrage mehr als einmal ausgeführt? Ich würde Tabellendefinition, Kardinalitäten und genaue Abfrage sehen müssen, um mehr zu sagen.
Erwin Brandstetter
1
Dies ist keine echte Antwort. Viele vielseitige Werkzeuge können ebenso wie spezifischere Werkzeuge arbeiten. Wenn Vielseitigkeit in der Tat dazu beiträgt, dass es langsamer wird, was ist dann mit seiner Vielseitigkeit?
Gavin Wahl
1
@ Jeffrey: Scheint, als würde Postgres für jede Variante einen anderen Sortieralgorithmus auswählen (basierend auf Kostenschätzungen und Tabellenstatistiken). Am Ende wird eine schlechtere Methode für den ARRAY-Konstruktor ausgewählt, die darauf hinweist, dass ein oder mehrere Faktoren bei der Berechnung der geschätzten Kosten zu weit entfernt sind. Ist das auf einem temporären Tisch? Haben Sie VACUUM ANALYZEes getan , bevor Sie die Abfragen ausgeführt haben? Beachten
Erwin Brandstetter
5

Ich glaube, die akzeptierte Antwort von Erwin könnte wie folgt ergänzt werden.

Normalerweise arbeiten wir mit regulären Tabellen mit Indizes anstelle von temporären Tabellen (ohne Indizes) wie in der ursprünglichen Frage. Beachten Sie, dass Aggregationen, wie z. B. ARRAY_AGGvorhandene Indizes, nicht nutzen können, wenn die Sortierung während der Aggregation durchgeführt wird .

Nehmen Sie beispielsweise die folgende Abfrage an:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Wenn ein Index aktiviert ist t(id, ...), kann der Index anstelle eines sequentiellen Scan-Vorgangs tund eines Sortiervorgangs verwendet werden t.id. Wenn die Ausgabespalte, die in das Array (hier c) eingeschlossen wird, Teil des Index ist (z. B. ein Index ein t(id, c)oder ein Einschlussindex ein t(id) include(c)), kann dies sogar ein Nur-Index-Scan sein.

Nun schreiben wir diese Abfrage 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 wird immer ein sequentieller Scan sein, tgefolgt 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 v11, der Fall sein.

pbillen
quelle
2
Guter Punkt. Aber in aller Fairness, fragt mit array_agg()oder ähnlichen Aggregatfunktionen können noch Leverage Indizes mit einer Unterabfrage wie: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. Die Per-Aggregate- ORDER BYKlausel schließt die Verwendung von Indizes in Ihrem Beispiel aus. Ein Array-Konstruktor ist schneller als array_agg()wenn entweder derselbe Index verwendet werden kann (oder keiner). Es ist einfach nicht so vielseitig. Siehe: dba.stackexchange.com/a/213724/3684
Erwin Brandstetter
1
Richtig, das ist eine wichtige Unterscheidung. Ich habe meine Antwort leicht geändert, um zu verdeutlichen, dass diese Bemerkung nur gilt, wenn die Aggregationsfunktion sortiert werden muss. Sie könnten im einfachen Fall tatsächlich noch vom Index profitieren, da PostgreSQL eine gewisse Garantie dafür zu geben scheint, dass die Aggregation in derselben Reihenfolge wie in der Unterabfrage definiert erfolgt, wie im Link erläutert. Das ist ziemlich cool. Ich frage mich jedoch, ob dies bei partitionierten Tabellen und / oder FDW-Tabellen und / oder parallelen Workern immer noch zutrifft und ob PostgreSQL dieses Versprechen in zukünftigen Releases einhalten kann.
pbillen
Ich hatte keineswegs die Absicht, an der akzeptierten Antwort zu zweifeln. Ich dachte nur, dass es eine gute Ergänzung zum Grund für die Existenz und Verwendung von Indizes in Kombination mit Aggregation ist.
pbillen
1
Es ist eine gute Ergänzung.
Erwin Brandstetter