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 cela array_agg()
ressemblait. 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 ont été surprenants. La méthode #OldSchoolCool a été massivement plus rapide: une accélération de 25%. De plus, la simplifier 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 est-elle 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 bits"
la source
array_agg
doit garder une trace de l'ordre de ses entrées où leARRAY
constructeur semble faire quelque chose à peu près équivalent à aUNION
comme expression en interne. Si je devais deviner, ilarray_agg
faudrait probablement plus de mémoire. Je n'ai pas été en mesure de le tester de manière exhaustive, mais sur PostgreSQL 9.6 fonctionnant sur Ubuntu 16.04, laARRAY()
requête avecORDER BY
utilisé une fusion externe et était plus lente que laarray_agg
requête. Comme vous l'avez dit, à moins de lire le code, votre réponse est la meilleure explication que nous ayons.array_agg()
est plus rapide que le constructeur de tableau? Pour un cas simple? Très peu probable, mais si c'est probablement le cas, car Postgres a basé sa décision pour un plan de requête sur des statistiques inexactes des paramètres de coût. Je n'ai jamais vuarray_agg()
surpasser un constructeur de tableau et j'ai testé plusieurs fois.VACUUM ANALYZE
fait avant d'exécuter les requêtes? Considérez: dba.stackexchange.com/a/18694/3684Je 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 tirer parti des indices existants lorsque le tri est effectué pendant l'agrégation .Par exemple, supposez la requête suivante:
Si nous avons un index activé
t(id, ...)
, l'index pourrait être utilisé, en faveur d'un balayage séquentiel activét
suivi d'un tri activét.id
. De plus, si la colonne de sortie enveloppée dans le tableau (icic
) fait partie de l'index (comme un index activét(id, c)
ou un index inclust(id) include(c)
), il peut même s'agir d'une analyse d'index uniquement.Maintenant, réécrivons cette requête comme suit:
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 une analyse séquentielle
t
suivie 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.
la source
array_agg()
ou des fonctions d' agrégation similaires peuvent encore des index de levier avec une sous - requête comme:SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub
. LaORDER BY
clause par agrégat est ce qui empêche l'utilisation de l'index dans votre exemple. Un constructeur de tableau est plus rapide quearray_agg()
lorsque l'un ou l'autre peut utiliser le même index (ou aucun). Ce n'est tout simplement pas aussi polyvalent. Voir: dba.stackexchange.com/a/213724/3684