212 votes

postgresql COUNT(DISTINCT ...) très lent

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 ; elle prend environ 7,5 secondes, alors qu'elle ne prend que 5 minutes.

 SELECT COUNT(x) FROM table;

ce qui prend environ 435 ms. Y a-t-il un moyen de modifier ma requête pour améliorer les performances ? J'ai essayé de grouper 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,5s.

416voto

Ankur Points 2964

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)

14voto

wildplasser Points 17900
-- 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 être produit par d'autres méthodes (fonctions de fenêtre).

4voto

Tometzky Points 8230

Si votre count(distinct(x)) est nettement plus lent que count(x) alors vous pouvez accélérer cette requête en maintenant le nombre de valeurs x dans une table différente, par exemple table_name_x_counts (x integer not null, x_count int not null) en utilisant des déclencheurs. Mais vos performances d'écriture en souffriront et si vous mettez à jour de multiples x dans une seule transaction, vous devrez le faire dans un ordre explicite pour éviter un éventuel blocage.

0voto

R.P Singh Points 61

Je cherchais aussi la même réponse, parce qu'à un moment donné, j'ai eu besoin total_count avec des valeurs distinctes avec limite/offset .

Parce que c'est un peu difficile à faire - Pour obtenir le nombre total de valeurs distinctes avec la limite/offset. Habituellement, il est difficile d'obtenir le nombre total avec limite/offset. Finalement, j'ai trouvé le moyen de le faire -

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X