postgresql COUNT (DISTINCT…) très lent

166

J'ai une requête SQL très simple:

SELECT COUNT(DISTINCT x) FROM table;

Ma table contient environ 1,5 million de lignes. Cette requête s'exécute assez lentement; cela prend environ 7,5 s, comparé à

 SELECT COUNT(x) FROM table;

ce qui prend environ 435 ms. Existe-t-il un moyen de modifier ma requête pour améliorer les performances? J'ai essayé de regrouper et de faire un comptage régulier, ainsi que de mettre un index sur x; les deux ont le même temps d'exécution de 7,5 s.

ferson2020
la source
Je ne pense pas. Obtenir les valeurs distinctes de 1,5 million de lignes va simplement être lent.
Ry-
5
Je viens de l'essayer en C #, obtenir les valeurs distinctes de 1,5 million d' entiers de la mémoire prend plus d'une seconde sur mon ordinateur. Donc je pense que vous n'avez probablement pas de chance.
Ry-
Le plan de requête dépendra beaucoup de la structure de la table (index) et du paramétrage des constantes de réglage (work) mem, effective_cache_size, random_page_cost). Avec un réglage raisonnable, la requête pourrait éventuellement être exécutée en moins d'une seconde.
wildplasser
Pourriez-vous être plus précis? Quels index et constantes de réglage seraient nécessaires pour l'obtenir en moins d'une seconde? Pour plus de simplicité, supposons qu'il s'agit d'une table à deux colonnes avec une clé primaire sur la première colonne y, et que je fais cette requête `` distincte '' sur une deuxième colonne x de type int, avec 1,5 million de lignes.
ferson2020
1
Veuillez inclure la définition de la table avec tous les index (la \dsortie de psqlest la bonne) et précisez la colonne avec laquelle vous avez un problème. Ce serait bien de voir les EXPLAIN ANALYZEdeux requêtes.
vyegorov

Réponses:

316

Vous pouvez utiliser ceci:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

C'est beaucoup plus rapide que:

COUNT(DISTINCT column_name)
Ankur
la source
38
requêtes saintes batman! Cela a accéléré mon nombre de postgres distinct de 190 à 4,5 whoa!
rogerdpack
20
J'ai trouvé ce fil sur www.postgresql.org qui traite de la même chose: lien . Une des réponses (par Jeff Janes) dit que COUNT (DISTINCT ()) trie la table pour faire son travail au lieu d'utiliser le hachage.
Ankur
5
@Ankur Puis-je vous poser une question? Puisqu'il COUNT(DISTINCT())effectue le tri, il sera certainement utile d'avoir un index sur le, en column_nameparticulier avec une quantité relativement petite de work_mem(où le hachage produira une quantité relativement importante de lots). Depuis cela, il n'est pas toujours mauvais d'utiliser COUNT (DISTINCT () _, n'est-ce pas?
St.Antario
2
@musmahn Count(column)ne compte que les valeurs non nulles. count(*)compte les lignes. Ainsi, le premier / le plus long comptera également la ligne nulle (une fois). Remplacez par count(column_name)pour qu'ils se comportent de la même manière.
GolezTrol
1
@ankur ce n'était pas très utile pour moi ... je n'ai pas obtenu d'amélioration remarquable.
Shiwangini
11
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

Résultats:

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

Le même plan que pour le CTE pourrait probablement aussi être produit par d'autres méthodes (fonctions de fenêtre)

wildplasser
la source
2
Avez-vous envisagé l'effet de la mise en cache? Si vous effectuez trois "Expliquer l'analyse" par la suite, le premier peut être lent à récupérer les choses du disque tandis que les deux derniers peuvent être rapides à récupérer de la mémoire.
tobixen
En effet: effective_cache_size est le premier paramètre à modifier. Le mien est de 2 Go, IIRC.
wildplasser
J'ai défini mon effective_cache_size à 2 Go, sans changement de performances. D'autres paramètres que vous suggéreriez de modifier? Si oui, à quoi?
ferson2020
1) Comment l' avez-vous réglé? (l'avez-vous HUP?) 2) Avez-vous réellement autant de mémoire disponible? 3) montrez-nous votre plan. 4) peut-être que ma machine est plus rapide ou la vôtre a plus de charge simultanée à gérer. @ ferson2020: Ok
wildplasser
Je l'ai défini avec l'instruction: SET effective_cache_size = '2GB'; J'ai autant de mémoire disponible. J'ai essayé d'inclure mon plan de requête, mais il ne rentre pas dans la zone de commentaire.
ferson2020
2

Si votre count(distinct(x))est beaucoup plus lent qu'alors, count(x)vous pouvez accélérer cette requête en conservant le nombre de valeurs x dans différentes tables, par exemple table_name_x_counts (x integer not null, x_count int not null), à l'aide de déclencheurs. Mais vos performances d'écriture en souffriront et si vous mettez à jour plusieurs xvaleurs en une seule transaction, vous devrez le faire dans un ordre explicite pour éviter un éventuel blocage.

Tometzky
la source
0

Je cherchais également la même réponse, car à un moment donné, j'avais besoin de total_count avec des valeurs distinctes avec limite / offset .

Parce que c'est peu difficile à faire - Pour obtenir le nombre total avec des valeurs distinctes avec limite / décalage. Il est généralement difficile d'obtenir le nombre total avec limite / décalage. Enfin j'ai eu le moyen de faire -

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

Les performances des requêtes sont également élevées.

Rana Pratap Singh
la source