web-dev-qa-db-fra.com

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

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?

16
Sam

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

Quelquefois, GROUP BY est plus rapide, car il prend un chemin de code différent.

Dans votre cas particulier , il ne semble pas que vous puissiez vous débarrasser de 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);

Ajouter user_id n'est utile que si vous obtenez les analyses d'index uniquement en dehors de cela. Suivez le lien pour plus de détails. Enlèverait le cher  Analyse de tas bitmap  de votre plan de requête, qui consomme 90% du temps de requête.

Votre sortie EXPLAIN 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 beaucoup plus rapide.

Si les intervalles de temps dans vos requêtes sont toujours les mêmes, un MATERIALIIZED VIEW pliage user_id par (project_id, <fixed time intervall>) irait un long chemin. 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" devrait vraiment être:

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

De côté:
N'utilisez pas time comme identifiant. C'est un mot réservé en SQL standard et un type de base en Postgres.

19
Erwin Brandstetter

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) à 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
Luan Huynh