postgresql COUNT (DISTINCT ...) molto lento


166

Ho una query SQL molto semplice:

SELECT COUNT(DISTINCT x) FROM table;

La mia tabella ha circa 1,5 milioni di righe. Questa query viene eseguita abbastanza lentamente; ci vogliono circa 7,5 secondi, rispetto a

 SELECT COUNT(x) FROM table;

che dura circa 435 ms. Esiste un modo per modificare la mia query per migliorare le prestazioni? Ho provato a raggruppare e fare un conteggio regolare, oltre a mettere un indice su x; entrambi hanno lo stesso tempo di esecuzione di 7,5 secondi.


Io non la penso così. Ottenere i valori distinti di 1,5 milioni di righe sarà solo lento.
Ry-

5
L'ho appena provato in C #, ottenere i valori distinti di 1,5 milioni di numeri interi dalla memoria richiede più di un secondo sul mio computer. Quindi penso che tu sia probabilmente sfortunato.
Ry-

Il piano di query dipenderà molto dalla struttura della tabella (indici) e dall'impostazione delle costanti di ottimizzazione (lavoro) mem, efficace_cache_size, random_page_cost). Con un'ottimizzazione ragionevole, la query potrebbe essere eseguita in meno di un secondo.
wildplasser,

Potresti essere più specifico? Quali indici e costanti di ottimizzazione sarebbero necessari per ottenerlo in meno di un secondo? Per semplicità, supponiamo che questa sia una tabella a due colonne con una chiave primaria sulla prima colonna y, e sto facendo questa query "distinta" su una seconda colonna x di tipo int, con 1,5 milioni di righe.
ferson2020,

1
Includi la definizione della tabella con tutti gli indici (l' \doutput di psqlè buono) e specifica la colonna con cui hai problemi. Sarebbe bello vedere EXPLAIN ANALYZEentrambe le domande.
vyegorov,

Risposte:


316

Puoi usare questo:

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

Questo è molto più veloce di:

COUNT(DISTINCT column_name)

38
domande sacre batman! Ciò ha accelerato il mio conteggio postgres distinto da 190 a 4.5 whoa!
rogerdpack,

20
Ho trovato questa discussione su www.postgresql.org che discute la stessa cosa: link . Una delle risposte (di Jeff Janes) afferma che COUNT (DISTINCT ()) ordina la tabella per fare il suo lavoro invece di usare l'hash.
Ankur,

5
@Ankur Posso farti una domanda? Poiché COUNT(DISTINCT())esegue l'ordinamento, sarà sicuramente utile disporre di un indice in column_nameparticolare con una quantità relativamente piccola di work_mem(dove l'hash produce una quantità relativamente elevata di batch). Da allora, non è sempre male usare COUNT (DISTINCT () _, no?
St.Antario

2
@musmahn Count(column)conta solo valori non nulli. count(*)conta le righe. Quindi il primo / più lungo conterà anche la riga null (una volta). Passa a count(column_name)per farli comportare allo stesso modo.
GolezTrol,

1
@ankur questo non è stato molto utile per me..non ho avuto alcun miglioramento notevole.
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
        ;

risultati:

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)

Lo stesso piano del CTE potrebbe probabilmente essere prodotto anche con altri metodi (funzioni della finestra)


2
Hai considerato l'effetto della memorizzazione nella cache? Se si eseguono tre "spiegazioni dell'analisi" successivamente, la prima potrebbe essere lenta a recuperare le cose dal disco, mentre la seconda potrebbe essere veloce da recuperare dalla memoria.
Tobixen,

In effetti: efficace_cache_size è la prima impostazione da modificare. Il mio è 2 GB, IIRC.
wildplasser,

Ho impostato il mio effettive_cache_size su 2 GB, senza alcun cambiamento nelle prestazioni. Altre impostazioni che suggeriresti di modificare? In tal caso, a cosa?
ferson2020,

1) come l' hai impostato? (l'hai fatto?) 2) Hai davvero tanta memoria disponibile? 3) mostraci il tuo piano. 4) forse la mia macchina è più veloce o la tua ha più carico simultaneo da affrontare. @ ferson2020: Ok
wildplasser,

L'ho impostato con l'affermazione: SET effect_cache_size = '2GB'; Ho così tanta memoria disponibile. Ho provato a includere il mio piano di query, ma non si adatta alla casella dei commenti.
ferson2020,

2

Se il tuo count(distinct(x))è significativamente più lento di count(x)allora, puoi velocizzare questa query mantenendo i conteggi dei valori x in una tabella diversa, ad esempio table_name_x_counts (x integer not null, x_count int not null)utilizzando i trigger. Ma le prestazioni di scrittura ne risentiranno e se aggiorni più xvalori in un'unica transazione, dovrai farlo in un ordine esplicito per evitare possibili deadlock.


0

Stavo anche cercando la stessa risposta, perché ad un certo punto avevo bisogno di total_count con valori distinti insieme a limite / offset .

Perché è poco complicato da fare - Per ottenere il conteggio totale con valori distinti insieme a limite / offset. Di solito è difficile ottenere il conteggio totale con limite / offset. Finalmente ho avuto il modo di fare -

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

Anche le prestazioni delle query sono elevate.

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.