web-dev-qa-db-fra.com

Pourquoi array_agg () est-il plus lent que le constructeur ARRAY () non agrégé?

Je revoyais juste un vieux code écrit pour PostgreSQL pré-8.4 , et j'ai vu quelque chose de vraiment chouette. Je me souviens avoir eu une fonction personnalisée pour faire une partie de cela dans la journée, mais j'ai oublié à quoi ressemblait pre -array_agg(). Pour examen, l'agrégation moderne est écrite comme ceci.

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

Cependant, il était une fois, il était écrit comme ça,

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

Donc, je l'ai essayé avec des données de test ..

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

Les résultats étaient surprenants. La méthode #OldSchoolCool était massivement plus rapide: une accélération de 25%. De plus, en le simplifiant sans l'ORDRE, a montré la même lenteur.

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

Alors, qu'est-ce qui se passe ici. Pourquoi array_agg, une fonction interne tellement plus lente que le vaudou SQL du planificateur?

Utilisation de " PostgreSQL 9.5.5 sur x86_64-pc-linux-gnu, compilé par gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64 -bit"

14
Evan Carroll

Il n'y a rien de "old school" ou de "dépassé" dans un constructeur ARRAY ( C'est ce qu'est ARRAY(SELECT x FROM foobar)). C'est toujours aussi moderne. Utilisez-le pour l'agrégation simple de tableaux.

Le manuel:

Il est également possible de construire un tableau à partir des résultats d'une sous-requête. Sous cette forme, le constructeur du tableau est écrit avec la clé Word ARRAY suivie d'une sous-requête entre parenthèses (non entre crochets).

La fonction fonction d'agrégation array_agg() est plus polyvalente en ce qu'elle peut être intégrée dans une liste SELECT avec plus de colonnes, éventuellement plus d'agrégations dans la même SELECT, et des groupes arbitraires peuvent être formés avec GROUP BY. Alors qu'un constructeur ARRAY ne peut renvoyer qu'un seul tableau à partir d'un SELECT renvoyant une seule colonne.

Je n'ai pas étudié le code source, mais il semblerait évident qu'un outil beaucoup plus polyvalent est également plus cher.

Une différence notable: le constructeur ARRAY renvoie un tableau vide ({}) Si aucune ligne ne se qualifie. array_agg() renvoie NULL pour la même chose.

17
Erwin Brandstetter

Je crois que la réponse acceptée par Erwin pourrait être ajoutée avec ce qui suit.

Habituellement, nous travaillons avec des tables régulières avec des indices, au lieu de tables temporaires (sans indices) comme dans la question d'origine. Il est utile de noter que les agrégations, telles que ARRAY_AGG, Ne peuvent pas exploiter les indices existants lorsque le tri est effectué pendant l'agrégation.

Par exemple, supposez la requête suivante:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Si nous avons un index sur t(id, ...), l'index pourrait être utilisé, en faveur d'un scan séquentiel sur t suivi d'un tri sur t.id. De plus, si la colonne de sortie enveloppée dans le tableau (ici c) fait partie de l'index (comme un index sur t(id, c) ou un index d'inclusion sur t(id) include(c) ), il peut même s'agir d'une analyse indexée uniquement.

Maintenant, réécrivons cette requête comme suit:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Maintenant, l'agrégation n'utilisera pas l'index et elle doit trier les lignes en mémoire (ou pire encore pour les grands ensembles de données, sur disque). Ce sera toujours un scan séquentiel sur t suivi d'une agrégation + tri.

Pour autant que je sache, cela n'est pas documenté dans la documentation officielle, mais peut être dérivé de la source. Cela devrait être le cas pour toutes les versions actuelles, v11 incluse.

7
pbillen