Come accelerare selezionare distinti?


16

Ho una selezione semplice distinta su alcuni dati di serie temporali:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

E ci vogliono 112 secondi. Ecco il piano di query:

http://explain.depesz.com/s/NTyA

La mia applicazione deve preformare molte operazioni distinte e conta in questo modo. Esiste un modo più veloce per ottenere questo tipo di dati?

Risposte:


19

Probabilmente non vuoi sentirlo, ma l'opzione migliore per accelerare SELECT DISTINCTè evitare DISTINCT di iniziare. In molti casi (non tutti!) Può essere evitato con una migliore progettazione del database o query migliori.

A volte, GROUP BYè più veloce, perché richiede un percorso di codice diverso.

Nel tuo caso particolare , non sembra che tu possa sbarazzartene DISTINCT. Ma potresti supportare la query con un indice specializzato se hai molte query di quel tipo:

CREATE INDEX foo ON events (project_id, "time", user_id);

L'aggiunta user_idè utile solo se si ottengono scansioni solo indice da questo. Segui il link per i dettagli. Rimuoverebbe la costosa scansione di heap di Bitmap dal piano di query, che consuma il 90% del tempo di query.

Il tuo EXPLAINoutput mi dice che la query deve condensare 2.491 utenti distinti su mezzo milione di righe corrispondenti. Questo non diventerà super-veloce, non importa quello che fai, ma può essere sostanzialmente più veloce.

Se gli intervalli di tempo nelle tue query sono sempre gli stessi, un MATERIALIIZED VIEWpieghevole user_idper (project_id, <fixed time intervall>)farebbe molta strada. Nessuna possibilità lì con intervalli di tempo variabili, però. Forse potresti almeno piegare gli utenti all'ora o qualche altra unità di tempo minimo, e questo acquisterebbe prestazioni sufficienti per giustificare il notevole sovraccarico.

Nitpick:
Molto probabilmente, i predicati su "time"dovrebbero essere davvero:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

A parte:
non usare timecome identificatore. È una parola riservata in SQL standard e un tipo di base in Postgres.


Ho letto un po 'di scansioni solo indice, ci proverò.
Sam,

Sfortunatamente, l'intervallo di tempo non è stato risolto.
Sam,

@Sam: Quindi quanto è stata più veloce la tua query di esempio con l'indice suggerito?
Erwin Brandstetter,

3
@edwin: non ho ancora provato la produzione. Tuttavia, ho eseguito la query originale sul mio locale (con gli stessi dati) e ci sono voluti 3678.780 ms. Quindi ho aggiunto l'indice e lo ha accelerato fino a 170.156 ms. Il piano ora contiene "Scansione solo indice usando pippo sugli eventi".
Sam,

1
@ Sam: bello! Questo è quello a cui stavo puntando.
Erwin Brandstetter,

2

Ecco il mio test sul caso di Sam e sulla risposta di Erwin

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

Erwin ha detto "Probabilmente non vorrai sentirlo, ma l'opzione migliore per accelerare SELEZIONA DISTINCT è evitare DISTINCT all'inizio. In molti casi (non tutti!) Può essere evitato con una migliore progettazione del database o query migliori ". Penso che abbia ragione, dovremmo evitare di usare "distinto, raggruppa per, ordina per" (se presente).

Ho incontrato una situazione come il caso di Sam e penso che Sam possa usare la partizione sulla tabella degli eventi per mese. Ridurrà la dimensione dei dati durante la query, ma è necessaria una funzione (pl / pgsql) da eseguire anziché la query sopra. La funzione troverà le partizioni appropriate (dipende dalle condizioni) per eseguire la query.


2
> Penso che abbia ragione, dovremmo evitare di usare "distinto, raggruppa per, ordina per" - e anche SELEZIONA, INSERISCI e AGGIORNA. Se evitiamo questi costrutti, il nostro database sarà molto veloce!
greatvovan,
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.