Comment puis-je forcer Postgres à utiliser un index alors qu'il insisterait autrement sur une analyse séquentielle?
En supposant que vous posiez des questions sur la fonctionnalité courante d'indexation d'index trouvée dans de nombreuses bases de données, PostgreSQL ™ ne fournit pas une telle fonctionnalité. C'était une décision consciente prise par l'équipe de PostgreSQL. Vous pouvez trouver un bon aperçu de pourquoi et de ce que vous pouvez faire à la place ici . Les raisons en sont essentiellement que c'est un piratage des performances qui a tendance à causer plus de problèmes plus tard, à mesure que vos données changent, alors que l'optimiseur de PostgreSQL peut réévaluer le plan en fonction des statistiques. En d'autres termes, ce qui pourrait être un bon plan de requête aujourd'hui ne sera probablement pas un bon plan de requête pour toujours, et les indicateurs d'index forcent un plan de requête particulier pour toujours.
En tant que marteau très émoussé, utile pour les tests, vous pouvez utiliser le enable_seqscan
et enable_indexscan
paramètres. Voir:
Celles-ci ne conviennent pas à une utilisation en production continue . Si vous avez des problèmes avec le choix du plan de requête, vous devriez voir la documentation permettant de suivre les problèmes de performances de la requête . Ne vous contentez pas de définir enable_
params et s’éloigne.
Si vous n’avez pas une très bonne raison d’utiliser l’indice, Postgres peut faire le bon choix. Pourquoi?
Voir aussi cet ancien post de groupe de discussion .
Probablement la seule raison valable pour utiliser
set enable_seqscan=false
c'est quand vous écrivez des requêtes et que vous voulez voir rapidement quel serait le plan de requête s'il y avait de grandes quantités de données dans les tables. Ou bien sûr, si vous devez rapidement confirmer que votre requête n'utilise pas d'index simplement parce que le jeu de données est trop petit.
Parfois, PostgreSQL ne parvient pas à faire le meilleur choix d’index pour une condition particulière. Par exemple, supposons qu'il existe une table de transactions avec plusieurs millions de lignes, dont il y en a plusieurs centaines pour un jour donné, et que la table comporte quatre index: transaction_id, client_id, date et description. Vous voulez exécuter la requête suivante:
SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
description = 'Refund'
GROUP BY client_id
PostgreSQL peut choisir d'utiliser l'index transactions_description_idx au lieu de transactions_date_idx, ce qui peut entraîner plusieurs minutes de requête au lieu de moins d'une seconde. Si tel est le cas, vous pouvez forcer l'utilisation de l'index sur la date en masquant la condition de la manière suivante:
SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
description||'' = 'Refund'
GROUP BY client_id
La question sur elle-même est très invalide. Forcer (en activant enable_seqscan = off par exemple) est une très mauvaise idée. Il peut être utile de vérifier si ce sera plus rapide, mais le code de production ne devrait jamais utiliser de telles astuces.
Au lieu de cela, expliquez votre analyse, lisez-la et découvrez pourquoi PostgreSQL choisit un mauvais plan (selon vous).
Il existe sur le Web des outils d'aide à la lecture qui expliquent l'analyse des résultats. L'un d'eux est explique.depesz.com - écrit par moi.
Une autre option consiste à rejoindre le canal #postgresql sur le réseau freenode irc et à parler avec des gars là-bas pour vous aider - car l'optimisation de la requête ne consiste pas à "poser une question, obtenir une réponse, soyez heureux". cela ressemble plus à une conversation, avec beaucoup de choses à vérifier, beaucoup de choses à apprendre.
Ce problème survient généralement lorsque le coût estimé d'une analyse d'index est trop élevé et ne reflète pas correctement la réalité. Vous devrez peut-être abaisser le random_page_cost
paramètre de configuration pour résoudre ce problème. De la documentation Postgres :
Si vous réduisez cette valeur, le système [...] préférera les analyses d'index; le relancer rendra les analyses d'index plus coûteuses.
Vous pouvez vérifier si une valeur inférieure incitera réellement Postgres à utiliser l'index (utilisez-le uniquement pour tester uniquement ):
EXPLAIN <query>; # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>; # May use index scan now
Vous pouvez restaurer la valeur par défaut avec SET random_page_cost = DEFAULT;
encore.
Les analyses d'index nécessitent des extractions de page de disque non séquentielles. Postgres utilise random_page_cost
pour estimer le coût de tels extractions non séquentielles par rapport aux extractions séquentielles. La valeur par défaut est 4.0
, supposant ainsi un facteur de coût moyen de 4 par rapport aux extractions séquentielles (en tenant compte des effets de mise en cache).
Cependant, le problème est que cette valeur par défaut ne convient pas dans les scénarios importants suivants de la vie réelle:
1) Lecteurs SSD
Comme le reconnaît la documentation:
Stockage qui a un faible coût de lecture aléatoire par rapport à séquentiel, par ex. disques SSD, pourrait être mieux modélisé avec une valeur inférieure pour
random_page_cost
.
Selon le dernier point de cette diapositive tiré d'un discours à PostgresConf 2018, random_page_cost
devrait être réglé sur quelque chose entre 1.0
et 2.0
pour les disques SSD.
2) Données mises en cache
Si les données d'index requises sont déjà mises en cache dans la RAM, une analyse d'index sera toujours beaucoup plus rapide qu'une analyse séquentielle. La documentation dit:
De manière correspondante, si vos données sont susceptibles d'être entièrement en cache, diminuez [...]
random_page_cost
peut être approprié.
Le problème est que vous ne pouvez évidemment pas savoir facilement si les données pertinentes sont déjà mises en cache. Toutefois, si un index spécifique est fréquemment interrogé et si le système dispose de suffisamment de RAM, les données risquent d'être mises en cache et random_page_cost
devrait être réglé sur une valeur inférieure. Vous devrez expérimenter différentes valeurs et voir ce qui vous convient.
Vous pouvez également utiliser l’extension pg_prewarm pour la mise en cache explicite des données.
Il y a une astuce pour pousser postgres à préférer un seqscan en ajoutant un OFFSET 0
dans la sous-requête
C'est pratique pour optimiser les requêtes qui lient des tables volumineuses/énormes lorsque tout ce dont vous avez besoin est uniquement les n premiers/derniers éléments.
Disons que vous recherchez les 20 premiers/derniers éléments impliquant plusieurs tables ayant 100 000 entrées (ou plus), il est inutile de construire/lier toute la requête sur toutes les données lorsque ce que vous recherchez se trouve dans les 100 ou 1000 premiers. les entrées. Dans ce scénario, par exemple, il est 10 fois plus rapide d'effectuer une analyse séquentielle.
see Comment puis-je empêcher Postgres d'aligner une sous-requête?