J'utilise PostgreSQL 9.1 sur Ubuntu 12.04.
Je dois sélectionner des enregistrements dans une plage de temps: ma table time_limits
a deux timestamp
champs et une integer
propriété. Il y a des colonnes supplémentaires dans ma table réelle qui ne sont pas impliquées dans cette requête.
create table (
start_date_time timestamp,
end_date_time timestamp,
id_phi integer,
primary key(start_date_time, end_date_time,id_phi);
Cette table contient environ 2 millions d'enregistrements.
Les requêtes suivantes ont pris énormément de temps:
select * from time_limits as t
where t.id_phi=0
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time >= timestamp'2010-08-08 00:05:00';
J'ai donc essayé d'ajouter un autre index - l'inverse du PK:
create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);
J'ai l'impression que les performances se sont améliorées: le temps d'accès aux enregistrements au milieu de la table semble être plus raisonnable: entre 40 et 90 secondes.
Mais il reste encore plusieurs dizaines de secondes pour les valeurs situées au milieu de la plage de temps. Et deux fois plus lorsque vous visez la fin de la table (chronologiquement parlant).
J'ai essayé explain analyze
pour la première fois d'obtenir ce plan de requête:
Bitmap Heap Scan on time_limits (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
-> Bitmap Index Scan on idx_time_limits_phi_start_end (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
Total runtime: 44.507 ms
Voir les résultats sur depesz.com.
Que puis-je faire pour optimiser la recherche? Vous pouvez voir tout le temps est passé numérise une fois que les deux colonnes horodatages id_phi
est définie sur 0
. Et je ne comprends pas le gros balayage (60 000 lignes!) Sur les horodatages. Ne sont-ils pas indexés par la clé primaire et idx_inversed
j'ai ajouté?
Devrais-je changer de type d'horodatage à autre chose?
J'ai lu un peu sur les index GIST et GIN. Je suppose qu'ils peuvent être plus efficaces à certaines conditions pour les types personnalisés. Est-ce une option viable pour mon cas d'utilisation?
la source
explain analyze
sortie est l'heure à laquelle la requête a été requise sur le serveur . Si votre requête prend 45 secondes, le temps supplémentaire est utilisé pour transférer les données de la base de données au programme qui exécute la requête. Après tout, c'est 62682 lignes et si chaque ligne est grande (par exemple, a longvarchar
outext
colonnes), cela peut avoir un impact sur le temps de transfert drastiquement.rows=62682 rows
est l' estimation du planificateur . La requête renvoie 0 ligne.(actual time=44.446..44.446 rows=0 loops=1)
Réponses:
Pour Postgres 9.1 ou version ultérieure:
Dans la plupart des cas, l'ordre de tri d'un index n'est guère pertinent. Postgres peut scanner en arrière pratiquement aussi rapidement. Mais pour les requêtes de plage sur plusieurs colonnes, cela peut faire une énorme différence. Étroitement liés:
Considérez votre requête:
L'ordre de tri de la première colonne
id_phi
de l'index est sans importance. Puisqu'il est coché pour l' égalité (=
), cela devrait venir en premier. Tu as totalement raison. Plus dans cette réponse liée:Les postgres peuvent passer rapidement à
id_phi = 0
et prendre en compte les deux colonnes suivantes de l'index correspondant. Celles-ci sont interrogées avec des conditions de plage d'ordre de tri inversé (<=
,>=
). Dans mon index, les lignes qualifiantes viennent en premier. Devrait être le moyen le plus rapide possible avec un index B-Tree 1 :start_date_time <= something
: index a le timestamp le plus ancien en premier.Recurse jusqu'à ce que la première ligne ne se qualifie pas (super rapide).
end_date_time >= something
: index a d'abord le dernier timestamp.Continuer avec la valeur suivante pour la colonne 2.
Postgres peut numériser en avant ou en arrière. De la même manière que vous aviez l'index, il doit lire toutes les lignes qui correspondent sur les deux premières colonnes et ensuite filtrer sur la troisième. Assurez-vous de lire le chapitre Index et
ORDER BY
dans le manuel. Cela correspond assez bien à votre question.Combien de lignes correspondent sur les deux premières colonnes?
Seuls quelques-uns avec un
start_date_time
proche du début de la plage de temps de la table. Mais presque toutes les lignes avecid_phi = 0
à la fin chronologique de la table! Ainsi, les performances se détériorent avec les heures de début plus tardEstimations du planificateur
Le planificateur estime
rows=62682
votre requête d'exemple. Parmi ceux-ci, aucun n'est admissible (rows=0
). Vous obtiendrez peut-être de meilleures estimations si vous augmentez la cible statistique du tableau. Pour 2.000.000 lignes ...... pourrait payer. Ou même plus haut. Plus dans cette réponse liée:
Je suppose que vous n’avez pas besoin de cela pour
id_phi
(seulement quelques valeurs distinctes, réparties de manière égale), mais pour les horodatages (beaucoup de valeurs distinctes, réparties de manière inégale).Je ne pense pas non plus que l’important avec l’indice amélioré compte beaucoup.
CLUSTER
/ pg_repackSi vous le souhaitez plus rapidement, vous pouvez rationaliser l'ordre physique des lignes de votre tableau. Si vous pouvez vous permettre de verrouiller votre table exclusivement pendant une courte période (aux heures creuses, par exemple), réécrivez votre table et ordonnez les lignes en fonction de l'index:
Avec un accès simultané, considérez pg_repack , qui peut faire la même chose sans verrou exclusif.
Quoi qu'il en soit, l'effet est que moins de blocs doivent être lus dans la table et que tout est trié au préalable. C'est un effet ponctuel qui se détériore avec le temps, les écritures sur la table fragmentant l'ordre de tri physique.
Indice GiST dans Postgres 9.2+
1 Avec la page 9.2+, il existe une autre option, peut-être plus rapide: un index GiST pour une colonne d'intervalle.
Il existe des types de plage intégrés pour
timestamp
ettimestamp with time zone
:tsrange
,tstzrange
. Un index btree est généralement plus rapide pour uneinteger
colonne supplémentaire commeid_phi
. Plus petit et moins cher à entretenir, aussi. Mais la requête sera probablement toujours plus rapide dans l'ensemble avec l'index combiné.Changez la définition de votre table ou utilisez un index d'expression .
Pour l’index GiST multicolonne disponible, vous devez également
btree_gist
installer le module supplémentaire (une fois par base de données), qui fournit aux classes d’opérateurs l’inclusion d’uninteger
.Le trifecta! Un index GiST fonctionnel multicolonne :
Utilisez maintenant l' opérateur "contient la plage"
@>
dans votre requête:Indice SP-GiST dans Postgres 9.3+
Un index SP-GiST peut être encore plus rapide pour ce type de requête, à ceci près que, citant le manuel :
Toujours vrai dans Postgres 12.
Il vous faudrait combiner un
spgist
index uniquement(tsrange(...))
avec un deuxièmebtree
index(id_phi)
. Avec les frais généraux ajoutés, je ne suis pas sûr que cela puisse rivaliser.Réponse associée avec un repère pour une
tsrange
colonne seulement:la source
La réponse d'Erwin est cependant déjà complète:
Les types de plage pour les horodatages sont disponibles dans PostgreSQL 9.1 avec l'extension Temporal de Jeff Davis: https://github.com/jeff-davis/PostgreSQL-Temporal
Remarque: a des fonctionnalités limitées (utilise Timestamptz, et vous ne pouvez avoir qu'un chevauchement de style '[)'). En outre, il existe de nombreuses autres bonnes raisons de mettre à niveau vers PostgreSQL 9.2.
la source
Vous pouvez essayer de créer l'index multicolonne dans un ordre différent:
J'ai posté une fois une question similaire concernant également le classement des index sur un index multicolonne. La clé consiste à essayer d’abord d’utiliser les conditions les plus restrictives pour réduire l’espace de recherche.
Edit : mon erreur. Maintenant, je vois que vous avez déjà défini cet index.
la source
Bitmap Index Scan on idx_time_limits_phi_start_end
J'ai réussi à augmenter rapidement (de 1 seconde à 70 ms)
J'ai un tableau avec des agrégations de nombreuses mesures et de nombreux niveaux (
l
colonne) (30s, 1m, 1h, etc.), il y a deux colonnes liées par plage:$s
pour le début et$e
pour la fin.J'ai créé deux index multicolonnes: un pour le début et un pour la fin.
J'ai ajusté la requête de sélection: sélection des plages où leur limite de départ est comprise dans une plage donnée. De plus, sélectionnez des plages où leur extrémité est dans la plage donnée.
Explain montre deux flux de lignes utilisant nos index efficacement.
Index:
Sélectionnez la requête:
Explique:
L'astuce est que vos nœuds de plan ne contiennent que des lignes utiles. Auparavant, nous avions des milliers de lignes dans le nœud du plan car celui-ci avait été sélectionné
all points from some point in time to the very end
, puis le nœud suivant supprimait les lignes inutiles.la source