PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Postgres n'utilise pas l'index lorsque l'analyse de l'index est une bien meilleure option

Analyse d'index (uniquement) --> Analyse d'index bitmap --> Analyse séquentielle

Pour quelques lignes, il est avantageux d'exécuter une analyse d'index. Si suffisamment de pages de données sont visibles pour tous (=suffisamment aspirées et pas trop de charge d'écriture simultanée) et que l'index peut fournir toutes les valeurs de colonne nécessaires, un balayage plus rapide de l'index uniquement est utilisé. Avec plus de lignes attendues à renvoyer (pourcentage plus élevé du tableau et en fonction de la distribution des données, des fréquences de valeur et de la largeur des lignes), il devient plus probable de trouver plusieurs lignes sur une page de données. Ensuite, il vaut la peine de passer à des analyses d'index bitmap. (Ou pour combiner plusieurs index distincts.) Une fois qu'un grand pourcentage de pages de données doit être visité de toute façon, il est moins coûteux d'exécuter une analyse séquentielle, de filtrer les lignes excédentaires et d'ignorer complètement la surcharge des index.

L'utilisation de l'index devient (beaucoup) moins chère et plus probable lorsque l'accès aux pages de données dans un ordre aléatoire n'est pas (beaucoup) plus cher que d'y accéder dans un ordre séquentiel. C'est le cas lorsque vous utilisez un SSD au lieu de faire tourner des disques, ou plus encore, plus il y a de cache dans la RAM - et les paramètres de configuration respectifs random_page_cost et effective_cache_size sont définis en conséquence.

Dans votre cas, Postgres passe à une analyse séquentielle, s'attendant à trouver rows=263962 , c'est déjà 3 % de l'ensemble du tableau. (Alors que seuls rows=47935 sont effectivement trouvés, voir ci-dessous.)

Plus dans cette réponse connexe :

  • Requête PostgreSQL efficace sur l'horodatage à l'aide d'un balayage d'index ou d'index bitmap ?

Méfiez-vous des plans de requête forcés

Vous ne pouvez pas forcer une certaine méthode de planification directement dans Postgres, mais vous pouvez en créer autre semblent extrêmement coûteuses à des fins de débogage. Voir Configuration de la méthode du planificateur dans le manuel.

SET enable_seqscan = off (comme suggéré dans une autre réponse) le fait pour les analyses séquentielles. Mais cela est destiné à des fins de débogage dans votre session uniquement. Ne pas utilisez-le comme paramètre général en production à moins que vous ne sachiez exactement ce que vous faites. Cela peut forcer des plans de requête ridicules. Le manuel :

Ces paramètres de configuration fournissent une méthode grossière pour influencer les plans de requête choisis par l'optimiseur de requête. Si le plan par défaut choisi par l'optimiseur pour une requête particulière n'est pas optimal, un temporaire La solution consiste à utiliser l'un de ces paramètres de configuration pour forcer l'optimiseur à choisir un plan différent. Les meilleurs moyens d'améliorer la qualité des plans choisis par l'optimiseur incluent l'ajustement des constantes de coût du planificateur (voir Section 19.7.2), l'exécution de ANALYZE manuellement, en augmentant la valeur de default_statistics_target paramètre de configuration et en augmentant la quantité de statistiques collectées pour des colonnes spécifiques à l'aide de ALTER TABLE SET STATISTICS .

C'est déjà la plupart des conseils dont vous avez besoin.

  • Empêcher PostgreSQL de choisir parfois un mauvais plan de requête

Dans ce cas particulier, Postgres attend 5 à 6 fois plus de visites sur email_activities.email_recipient_id que celles réellement trouvées :

estimé rows=227007 vs actual ... rows=40789
estimé rows=263962 vs actual ... rows=47935

Si vous exécutez souvent cette requête, il sera payant d'avoir ANALYZE regardez un échantillon plus grand pour des statistiques plus précises sur la colonne particulière. Votre tableau est grand (~ 10 millions de lignes), alors faites-le :

ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000;  -- max 10000, default 100

Puis ANALYZE email_activities;

Mesure de dernier recours

En très rare cas où vous pourriez avoir recours pour forcer un index avec SET LOCAL enable_seqscan = off dans une transaction distincte ou dans une fonction avec son propre environnement. Comme :

CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
  RETURNS bigint AS
$func$
   SELECT COUNT(DISTINCT a.email_recipient_id)
   FROM   email_activities a
   WHERE  a.email_recipient_id IN (
      SELECT id
      FROM   email_recipients
      WHERE  email_campaign_id = $1
      LIMIT  $2)       -- or consider query below
$func$  LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;

Le paramètre s'applique uniquement à la portée locale de la fonction.

Avertissement : Ceci est juste une preuve de concept. Même cette intervention manuelle beaucoup moins radicale pourrait vous mordre à long terme. Les cardinalités, les fréquences de valeur, votre schéma, les paramètres globaux de Postgres, tout change avec le temps. Vous allez effectuer une mise à niveau vers une nouvelle version de Postgres. Le plan de requête que vous forcez maintenant peut devenir une très mauvaise idée plus tard.

Et généralement, il ne s'agit que d'une solution de contournement à un problème lié à votre configuration. Mieux vaut le trouver et le réparer.

Requête alternative

Des informations essentielles manquent dans la question, mais cette requête équivalente est probablement plus rapide et plus susceptible d'utiliser un index sur (email_recipient_id ) - de plus en plus pour un LIMIT plus grand .

SELECT COUNT(*) AS ct
FROM  (
   SELECT id
   FROM   email_recipients
   WHERE  email_campaign_id = 1607
   LIMIT  43000
   ) r
WHERE  EXISTS (
   SELECT FROM email_activities
   WHERE  email_recipient_id = r.id);