J'ai une table station_logs
dans une base de données PostgreSQL 9.6:
Column | Type |
---------------+-----------------------------+
id | bigint | bigserial
station_id | integer | not null
submitted_at | timestamp without time zone |
level_sensor | double precision |
Indexes:
"station_logs_pkey" PRIMARY KEY, btree (id)
"uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)
J'essaie d'obtenir la dernière level_sensor
valeur basée sur submitted_at
, pour chacun station_id
. Il existe environ 400 station_id
valeurs uniques et environ 20 000 lignes par jour et par station_id
.
Avant de créer un index:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Unique (coût = 4347852.14..4450301.72 lignes = 89 largeur = 20) (temps réel = 22202.080..27619.167 lignes = 98 boucles = 1) -> Trier (coût = 4347852.14..4399076.93 lignes = 20489916 largeur = 20) (temps réel = 22202.077..26540.827 lignes = 20489812 boucles = 1) Clé de tri: station_id, submit_at DESC Méthode de tri: fusion externe Disque: 681040 Ko -> Seq Scan sur station_logs (coût = 0,00 à 598895,16 lignes = 20489916 largeur = 20) (temps réel = 0,023 à 3443,587 lignes = 20489812 boucles = $ Temps de planification: 0,072 ms Temps d'exécution: 27690,644 ms
Création d'un index:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Après avoir créé l'index, pour la même requête:
Unique (coût = 0,56..2156367,51 lignes = 89 largeur = 20) (temps réel = 0,184..16263,413 lignes = 98 boucles = 1) -> Index Scan en utilisant station_id__submitted_at sur station_logs (coût = 0,56..2105142,98 lignes = 20489812 largeur = 20) (temps réel = 0,181..1 $ Temps de planification: 0,206 ms Temps d'exécution: 16263,490 ms
Existe-t-il un moyen d'accélérer cette requête? Comme 1 seconde par exemple, 16 secondes, c'est encore trop.
Réponses:
Pour seulement 400 stations, cette requête sera massivement plus rapide:
dbfiddle ici
(en comparant les plans pour cette requête, l'alternative d'Abelisto et votre original)
Résultat
EXPLAIN ANALYZE
tel que fourni par le PO:Le seul indice dont vous avez besoin est celui que vous avez créé:
station_id__submitted_at
. LaUNIQUE
contrainte faituniq_sid_sat
également le travail, essentiellement. La maintenance des deux semble être une perte d'espace disque et de performances d'écriture.J'ai ajouté
NULLS LAST
àORDER BY
dans la requête carsubmitted_at
n'est pas définiNOT NULL
. Idéalement, le cas échéant !, ajoutez uneNOT NULL
contrainte à la colonnesubmitted_at
, supprimez l'index supplémentaire et supprimezNULLS LAST
de la requête.Si
submitted_at
possibleNULL
, créez cetUNIQUE
index pour remplacer à la fois votre index actuel et votre contrainte unique:Considérer:
Cela suppose une table distincte
station
avec une ligne par pertinencestation_id
(généralement le PK) - que vous devriez avoir dans les deux cas. Si vous ne l'avez pas, créez-le. Encore une fois, très rapide avec cette technique rCTE:Je l'utilise aussi au violon. Vous pouvez utiliser une requête similaire pour résoudre votre tâche directement, sans
station
table - si vous ne pouvez pas être convaincu de la créer.Instructions détaillées, explication et alternatives:
Optimiser l'index
Votre requête devrait être très rapide maintenant. Seulement si vous devez encore optimiser les performances de lecture ...
Il pourrait être judicieux d'ajouter
level_sensor
comme dernière colonne à l'index pour autoriser les analyses d'index uniquement , comme l' a commenté joanolo .Con: il rend l'index plus grand - ce qui ajoute un peu de coût à toutes les requêtes qui l'utilisent.
Pro: Si vous obtenez réellement des analyses d'index uniquement, la requête à portée de main n'a pas du tout à visiter les pages de tas, ce qui la rend environ deux fois plus rapide. Mais cela peut être un gain non substantiel pour la requête très rapide maintenant.
Cependant , je ne m'attends pas à ce que cela fonctionne pour votre cas. Vous avez mentionné:
En règle générale, cela indiquerait une charge d'écriture incessante (1
station_id
toutes les 5 secondes). Et vous êtes intéressé par la dernière ligne. Les analyses d'index uniquement ne fonctionnent que pour les pages de segment visibles par toutes les transactions (le bit dans la carte de visibilité est défini). Vous devez exécuter desVACUUM
paramètres extrêmement agressifs pour que la table suive la charge d'écriture, et cela ne fonctionnera toujours pas la plupart du temps. Si mes hypothèses sont correctes, les analyses d'index uniquement sont supprimées, n'ajoutez paslevel_sensor
à l'index.OTOH, si mes hypothèses se vérifient et que votre table grandit très , un indice BRIN pourrait vous aider. En relation:
Ou, encore plus spécialisé et plus efficace: un index partiel pour les derniers ajouts seulement pour couper la majeure partie des lignes non pertinentes:
Choisissez un horodatage pour lequel vous savez que des lignes plus jeunes doivent exister. Vous devez ajouter une
WHERE
condition de correspondance à toutes les requêtes, comme:Vous devez de temps en temps adapter l'index et la requête.
Réponses associées avec plus de détails:
la source
Essayez la méthode classique:
dbfiddle
EXPLAIN ANALYZE par ThreadStarter
la source