Mes requêtes deviennent très lentes lorsque j'ajoute un limit 1
.
J'ai une table object_values
avec des valeurs horodatées pour les objets:
timestamp | objectID | value
--------------------------------
2014-01-27| 234 | ksghdf
Par objet, je veux obtenir la dernière valeur:
SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp DESC LIMIT 1;
(J'ai annulé la requête après plus de 10 minutes)
Cette requête est très lente lorsqu'il n'y a pas de valeurs pour un objectID donné (elle est rapide s'il y a des résultats). Si je supprime la limite, cela me dit presque instantanément qu'il n'y a pas de résultats:
SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp DESC;
...
Time: 0.463 ms
Une explication me montre que la requête sans limite utilise l'index, alors que la requête avec limit 1
n'utilise pas l'index:
requête lente:
explain SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp DESC limit 1;
QUERY PLAN`
----------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..2350.44 rows=1 width=126)
-> Index Scan Backward using object_values_timestamp on object_values (cost=0.00..3995743.59 rows=1700 width=126)
Filter: (objectID = 53708)`
Requête rapide:
explain SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp DESC;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Sort (cost=6540.86..6545.11 rows=1700 width=126)
Sort Key: timestamp
-> Index Scan using object_values_objectID on working_hours_t (cost=0.00..6449.65 rows=1700 width=126)
Index Cond: (objectID = 53708)
Le tableau contient 44 884 559 lignes et 66 762 ID d'objet distincts.
J'ai des index distincts sur les deux champs: timestamp
et objectID
.
J'ai fait un vacuum analyze
sur la table et j'ai réindexé la table.
De plus, la requête lente devient rapide lorsque je fixe la limite à 3 ou plus:
explain SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp DESC limit 3;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Limit (cost=6471.62..6471.63 rows=3 width=126)
-> Sort (cost=6471.62..6475.87 rows=1700 width=126)
Sort Key: timestamp
-> Index Scan using object_values_objectID on object_values (cost=0.00..6449.65 rows=1700 width=126)
Index Cond: (objectID = 53708)
En général, je suppose que cela a à voir avec le planificateur faisant des hypothèses erronées sur les coûts d'exécution et choisit donc un plan d'exécution plus lent.
Est-ce la vraie raison? Y a-t-il une solution à cela?
Vous rencontrez un problème lié, je pense, au manque de statistiques sur les corrélations entre les lignes. Pensez à le signaler à pg-bugs pour référence s'il utilise la dernière version de Postgres.
L'interprétation que je suggérerais pour vos plans est la suivante:
limit 1
oblige Postgres à rechercher une seule ligne et, ce faisant, il suppose que votre object_id est suffisamment commun pour qu'il apparaisse assez rapidement dans un scan d'index.
Sur la base des statistiques que vous avez réfléchies, il est probable qu'il faudra lire ~ 70 lignes en moyenne pour trouver une ligne qui convient; il ne se rend tout simplement pas compte que object_id et timestamp sont corrélés au point où il va réellement lire une grande partie de la table.
limit 3
, en revanche, lui fait comprendre que c'est assez rare, donc il considère sérieusement (et finit par…) trier les n premiers 1700 lignes attendues avec le object_id
vous voulez, car cela revient probablement moins cher.
Par exemple, il peut savoir que la distribution de ces lignes est telle qu'elles sont toutes regroupées dans la même zone sur le disque.
aucune clause limit
signifie qu'elle récupérera le 1700 de toute façon, donc elle va directement pour l'index sur object_id
.
Solution, btw: ajoutez un index sur (object_id, timestamp)
ou (object_id, timestamp desc)
.
Vous pouvez éviter ce problème en ajoutant une clause ORDER BY
Inutile à la requête.
SELECT * FROM object_values WHERE (objectID = 53708) ORDER BY timestamp, objectID DESC limit 1;
J'ai commencé à avoir des symptômes similaires sur une table lourde de mises à jour, et ce qui était nécessaire dans mon cas était
analyze $table_name;
Dans ce cas, les statistiques devaient être actualisées, ce qui a ensuite corrigé les plans de requête lents qui se produisaient.
Documents de support: https://www.postgresql.org/docs/current/sql-analyze.html