Comment accélérer la sélection distincte?


16

J'ai une simple sélection distincte sur certaines données de séries chronologiques:

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';

Et cela prend 112 secondes. Voici le plan de requête:

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

Mon application doit effectuer de nombreuses opérations distinctes et compte comme ça. Existe-t-il un moyen plus rapide d'obtenir ce type de données?

Réponses:


19

Vous ne voulez probablement pas entendre cela, mais la meilleure option pour accélérer SELECT DISTINCTest d' éviter DISTINCT de commencer. Dans de nombreux cas (pas tous!), Cela peut être évité avec une meilleure conception de la base de données ou de meilleures requêtes.

Parfois, GROUP BYc'est plus rapide, car cela prend un chemin de code différent.

Dans votre cas particulier , il ne semble pas que vous puissiez vous en débarrasser DISTINCT. Mais vous pouvez prendre en charge la requête avec un index spécialisé si vous avez de nombreuses requêtes de ce type:

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

L'ajout user_idn'est utile que si vous obtenez des analyses d'index uniquement . Suivez le lien pour plus de détails. Supprime le scan de tas Bitmap coûteux de votre plan de requête, qui consomme 90% du temps de requête.

Votre EXPLAINrésultat me dit que la requête doit condenser 2 491 utilisateurs distincts sur un demi-million de lignes correspondantes. Cela ne deviendra pas ultra-rapide, quoi que vous fassiez, mais cela peut être considérablement plus rapide.

Si les intervalles de temps dans vos requêtes sont toujours les mêmes, un MATERIALIIZED VIEWpliage user_idpar (project_id, <fixed time intervall>)irait très loin. Aucune chance là-bas avec des intervalles de temps variables, cependant. Peut-être pourriez-vous au moins plier les utilisateurs par heure ou une autre unité de temps minimale, et cela permettrait d'acheter suffisamment de performances pour justifier les frais généraux considérables.

Nitpick:
Très probablement, les prédicats sur "time"devraient vraiment être:

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

À part:
Ne pas utiliser timecomme identifiant. C'est un mot réservé dans SQL standard et un type de base dans Postgres.


J'ai lu un peu sur les analyses d'index uniquement, je vais essayer.
Sam

Malheureusement, l'intervalle de temps n'est pas fixe.
Sam

@Sam: Dans quelle mesure votre exemple de requête a-t-il été plus rapide avec l'index suggéré?
Erwin Brandstetter

3
@edwin: Je n'ai pas encore essayé la production. Cependant, j'ai exécuté la requête d'origine sur mon local (avec les mêmes données) et cela a pris 3678,780 ms. Ensuite, j'ai ajouté l'index et il l'a accéléré jusqu'à 170,156 ms. Le plan contient désormais "Indexation uniquement en utilisant foo sur les événements".
Sam

1
@Sam: Nice! C'est ce que je visais.
Erwin Brandstetter

2

Voici mon test sur le cas de Sam et la réponse d'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 a déclaré: "Vous ne voulez probablement pas entendre cela, mais la meilleure option pour accélérer SELECT DISTINCT est d'éviter d'abord DISTINCT. Dans de nombreux cas (pas tous!), Cela peut être évité avec une meilleure conception de base de données ou de meilleures requêtes. ". Je pense qu'il a raison, nous devons éviter d'utiliser "distinct, grouper, classer par" (le cas échéant).

J'ai rencontré une situation comme le cas de Sam et je pense que Sam peut utiliser la partition sur la table des événements par mois. Cela réduira la taille de vos données lorsque vous interrogerez, mais vous aurez besoin d'une fonction (pl / pgsql) pour exécuter au lieu de la requête ci-dessus. La fonction trouvera les partitions appropriées (en fonction des conditions) pour exécuter la requête.


2
> Je pense qu'il a raison, nous devons éviter d'utiliser "distinct, grouper, classer par" - et aussi SELECT, INSERT et UPDATE. Si nous évitons ces constructions, notre base de données sera très rapide!
greatvovan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.