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 dedefault_statistics_target
paramètre de configuration et en augmentant la quantité de statistiques collectées pour des colonnes spécifiques à l'aide deALTER 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
vsactual ... rows=40789
estimérows=263962
vsactual ... 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);