web-dev-qa-db-fra.com

Index sur la clé primaire non utilisée dans une jointure simple

J'ai le tableau et les définitions d'index suivants:

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);

Pourquoi aucun des index sur munkalap_id n'est utilisé dans la requête suivante?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms

C'est la même chose même si j'ajoute un filtre:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms
17
dezso

Beaucoup de gens ont entendu des conseils selon lesquels des "scans séquentiels sont mauvais" et cherchent à les éliminer de leurs plans, mais ce n'est pas si simple. Si une requête va couvrir toutes les rangées d'une table, une analyse séquentielle est le moyen le plus rapide d'obtenir ces lignes. C'est pourquoi votre requête de jointure d'origine utilisait SEQ Scan, car toutes les lignes des deux tables étaient nécessaires.

Lors de la planification d'une requête, le planificateur de Postgres estime les coûts de diverses opérations (calcul, séquentielle et aléatoire IO) dans différents régimes possibles et choisit le plan qu'elle estime comme ayant le coût le plus bas. Lorsque vous faites IO de stockage rotatif (disques), aléatoire IO est généralement sensiblement plus lent que séquentiel IO, la configuration PG par défaut pour Random_Page_cost et SEQ_PAGE_COST Estimation d'une différence de coût de 4: 1.

Ces considérations entrent en jeu lorsqu'ils envisagent une méthode de jointure ou de filtrage qui utilise un indice vs celui qui scanne séquentiellement une table. Lors de l'utilisation d'un index, le plan peut trouver une ligne rapidement via l'index, puis il faut tenir compte d'un bloc aléatoire lu pour résoudre les données de la ligne. Dans le cas de votre deuxième requête qui a ajouté un prédicat filtrant WHERE NOT lezarva, vous pouvez voir comment cela a effectué les estimations de planification dans les résultats d'analyse d'explication. Le planificateur estime 1006 lignes résultant de la jointure (qui correspond assez étroitement à l'ensemble de résultats réels de 964). Étant donné que la table la plus grande munkalap_lepes contient environ 38 000 lignes, le planificateur voit que la jointure va avoir à accéder à environ 1006/38046 ou 1/38 des rangées de la table. Il sait également que la largeur de la rangée AVG est de 214 octets et un bloc est de 8k, il y a donc environ 38 rangées/blocs.

Avec ces statistiques, le planificateur considère probablement que la jointure devra lire tout ou la plupart des blocs de données de la table. Étant donné que les recherches d'index ne sont pas libres non plus et que le calcul de numérisation d'un bloc d'évaluation d'une condition de filtrage est très bon marché par rapport à IO, le planificateur a choisi de numériser séquentiellement la table et d'éviter les frais généraux d'index et des lectures aléatoires, car elle calcule la scanner SEQ sera plus rapide.

Dans le monde réel, les données sont souvent disponibles en mémoire via le cache de la page du système d'exploitation, et chaque bloc de lecture n'exige pas IO. Il peut être assez difficile de prédire à quel point une cache sera efficace pour une requête donnée, mais le planificateur PG utilise certaines heuristiques simples. La valeur de configuration effective_cache_size Informe les estimations des planificateurs de la probabilité d'engager des coûts réels IO. Une valeur plus grande entraînera d'estimer un coût inférieur à aléatoire IO et peut donc le biaiser vers une méthode d'index sur une analyse séquentielle.

25
dbenhur

Vous récupérez toutes les lignes des deux tables, il n'ya donc aucun avantage réel en utilisant une analyse d'index. Une analyse d'index n'a aucun sens si vous ne sélectionnez que quelques lignes d'une table (généralement inférieure à 10% à15%)

5