Perché array_agg () è più lento del costruttore ARRAY () non aggregato?


13

Stavo solo rivedendo un vecchio codice scritto per PostgreSQL pre-8.4 e ho visto qualcosa di veramente elegante. Ricordo di avere una funzione personalizzata per fare un po 'di tutto ciò nel corso della giornata, ma ho dimenticato come era pre array_agg(). Per la revisione, l'aggregazione moderna è scritta in questo modo.

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

Tuttavia, una volta, è stato scritto in questo modo,

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

Quindi, l'ho provato con alcuni dati di test ..

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

I risultati sono stati sorprendenti .. Il modo #OldSchoolCool è stato enormemente più veloce: uno speedup del 25%. Inoltre, semplificandolo senza l'ORDINE, ha mostrato la stessa lentezza.

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

Quindi, cosa sta succedendo qui. Perché array_agg , una funzione interna è molto più lenta del voodoo SQL del planner?

Utilizzando " PostgreSQL 9.5.5 su x86_64-pc-linux-gnu, compilato da gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64-bit"

Risposte:


17

Non c'è niente di "vecchia scuola" o "obsoleto" in un costruttore di ARRAY (ecco cos'è ARRAY(SELECT x FROM foobar)). È moderno come sempre. Usalo per una semplice aggregazione di array.

Il manuale:

È anche possibile costruire un array dai risultati di una sottoquery. In questo modulo, il costruttore della matrice è scritto con la parola chiave ARRAYseguita da una sottoquery tra parentesi (non tra parentesi).

La funzione aggregataarray_agg() è molto più versatile in quanto può essere integrata SELECTnell'elenco con più colonne, possibilmente più aggregazioni nella stessa SELECT, e con cui si possono formare gruppi arbitrari GROUP BY. Mentre un costruttore ARRAY può restituire un solo array da una SELECTcolonna singola di ritorno.

Non ho studiato il codice sorgente, ma sembrerebbe ovvio che uno strumento molto più versatile è anche più costoso.


array_aggdeve tenere traccia dell'ordine dei suoi input in cui il ARRAYcostruttore sembra fare qualcosa di approssimativamente equivalente a a UNIONcome espressione internamente. Se dovessi avventurarmi in un'ipotesi, array_aggprobabilmente richiederebbe più memoria. Non sono stato in grado di testare esaurientemente questo, ma su PostgreSQL 9.6 in esecuzione su Ubuntu 16.04 la ARRAY()query con ha ORDER BYusato un'unione esterna ed è stata più lenta della array_aggquery. Come hai detto, a meno di leggere il codice, la tua risposta è la migliore spiegazione che abbiamo.
Jeff

@Jeffrey: hai trovato un caso di test in cui array_agg()è più veloce del costruttore dell'array? Per un semplice caso? Molto improbabile, ma in tal caso probabilmente perché Postgres ha basato la sua decisione per un piano di query su statistiche imprecise delle impostazioni dei costi. Non ho mai visto array_agg()sovraperformare un costruttore di array e ho provato molte volte.
Erwin Brandstetter,

1
@Jeffrey: nessun effetto di memorizzazione nella cache fuorviante? Hai eseguito ogni query più di una volta? Avrei bisogno di vedere la definizione della tabella, le cardinalità e la query esatta per aggiungere altro.
Erwin Brandstetter,

1
Questa non è una vera risposta. Molti strumenti versatili possono eseguire così come strumenti più specifici. Se essere versatile è davvero ciò che lo rende più lento, che ne è della sua versatilità?
Gavin Wahl,

1
@Jeffrey: sembra che Postgres scelga un algoritmo di ordinamento diverso per ciascuna variante (basato su stime dei costi e statistiche delle tabelle). E finisce per scegliere un metodo inferiore per il costruttore ARRAY, che indica che uno o più fattori nel calcolo del costo stimato sono troppo lontani. Questo è su un tavolo temporaneo? L'hai VACUUM ANALYZEfatto prima di eseguire le query? Considerare: dba.stackexchange.com/a/18694/3684
Erwin Brandstetter

5

Credo che la risposta accettata da Erwin potrebbe essere aggiunta con quanto segue.

Di solito, stiamo lavorando con tabelle regolari con indici, anziché tabelle temporanee (senza indici) come nella domanda originale. È utile notare che le aggregazioni, come ad esempio ARRAY_AGG, non possono sfruttare gli indici esistenti quando l'ordinamento viene eseguito durante l'aggregazione .

Ad esempio, supponiamo che la seguente query:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Se abbiamo un indice attivo t(id, ...), l'indice potrebbe essere utilizzato, a favore di una scansione sequenziale attivata tseguita da un ordinamento attivo t.id. Inoltre, se la colonna di output che viene racchiusa nell'array (qui c) fa parte dell'indice (come un indice attivo t(id, c)o un indice incluso attivo t(id) include(c)), potrebbe trattarsi anche di una scansione solo indice.

Ora riscriviamo la query come segue:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Ora, l'aggregazione non utilizzerà l'indice e dovrà ordinare le righe in memoria (o peggio ancora per grandi set di dati, su disco). Questa sarà sempre una scansione sequenziale tseguita da aggregazione + ordinamento .

Per quanto ne so, questo non è documentato nella documentazione ufficiale, ma può essere derivato dalla fonte. Questo dovrebbe essere il caso di tutte le versioni correnti, v11 inclusa.


2
Buon punto. Ma in tutta onestà, query con array_agg()o funzioni di aggregazione simile può ancora indici di leva con una sottoquery come: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. La ORDER BYclausola per aggregato è ciò che preclude l'utilizzo dell'indice nel tuo esempio. Un costruttore di array è più veloce di array_agg()quando entrambi possono usare lo stesso indice (o nessuno dei due). Non è così versatile. Vedi: dba.stackexchange.com/a/213724/3684
Erwin Brandstetter,

1
Bene, questa è una distinzione importante da fare. Ho leggermente modificato la mia risposta per chiarire che questa osservazione vale solo quando la funzione di aggregazione deve essere ordinata. Potresti davvero trarre profitto dall'indice nel caso semplice, perché PostgreSQL sembra dare una garanzia che l'aggregazione avverrà nello stesso ordine definito nella sottoquery, come spiegato nel link. È abbastanza bello. Mi chiedo però se questo vale ancora nel caso di tabelle partizionate e / o tabelle FDW e / o lavoratori paralleli - e se PostgreSQL può mantenere questa promessa nelle versioni future.
pbillen,

Per la cronaca, non avevo assolutamente intenzione di dubitare della risposta accettata. Ho solo pensato che fosse una buona aggiunta alla ragione dell'esistenza e dell'uso degli indici in combinazione con l'aggregazione.
pbillen,

1
Si tratta di una buona aggiunta.
Erwin Brandstetter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.