Pour seulement 400 stations, cette requête sera massivement plus rapide:
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM station s
CROSS JOIN LATERAL (
SELECT submitted_at, level_sensor
FROM station_logs
WHERE station_id = s.station_id
ORDER BY submitted_at DESC NULLS LAST
LIMIT 1
) l;
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:
Boucle imbriquée (coût = 0,56..356,65 lignes = 102 largeur = 20) (temps réel = 0,034..0,979 lignes = 98 boucles = 1)
-> Seq Scan sur les stations s (coût = 0,00..3,02 lignes = 102 largeur = 4) (temps réel = 0,009..0,016 lignes = 102 boucles = 1)
-> Limite (coût = 0,56..3,45 lignes = 1 largeur = 16) (temps réel = 0,009..0,009 lignes = 1 boucles = 102)
-> Index Scan en utilisant station_id__submitted_at sur station_logs (coût = 0,56..664062,38 lignes = 230223 largeur = 16) (temps réel = 0,009 $
Index Cond: (station_id = s.id)
Temps de planification: 0,542 ms
Temps d'exécution: 1,013 ms - !!
Le seul indice dont vous avez besoin est celui que vous avez créé: station_id__submitted_at
. La UNIQUE
contrainte fait uniq_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 car submitted_at
n'est pas défini NOT NULL
. Idéalement, le cas échéant !, ajoutez une NOT NULL
contrainte à la colonne submitted_at
, supprimez l'index supplémentaire et supprimez NULLS LAST
de la requête.
Si submitted_at
possible NULL
, créez cet UNIQUE
index pour remplacer à la fois votre index actuel et votre contrainte unique:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Considérer:
Cela suppose une table distinctestation
avec une ligne par pertinence station_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:
CREATE TABLE station AS
WITH RECURSIVE cte AS (
(
SELECT station_id
FROM station_logs
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT l.station_id
FROM cte c
, LATERAL (
SELECT station_id
FROM station_logs
WHERE station_id > c.station_id
ORDER BY station_id
LIMIT 1
) l
)
TABLE cte;
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é:
... environ 20 000 lignes par jour et par station_id
.
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 des VACUUM
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:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
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:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Vous devez de temps en temps adapter l'index et la requête.
Réponses associées avec plus de détails: