Analyse séquentielle PostgreSQL au lieu de l'analyse d'index Pourquoi?


12

Salut à tous J'ai un problème avec ma requête de base de données PostgreSQL et je me demande si quelqu'un peut m'aider. Dans certains scénarios, ma requête semble ignorer l'index que j'ai créé qui est utilisé pour joindre les deux tables dataet data_area. Lorsque cela se produit, il utilise une analyse séquentielle et entraîne une requête beaucoup plus lente.

Balayage séquentiel (~ 5 minutes)

Unique  (cost=15368261.82..15369053.96 rows=200 width=1942) (actual time=301266.832..301346.936 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6086.77..610089.54 rows=321976 width=297) (actual time=26.286..197.625 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6006.27 rows=324789 width=0) (actual time=25.462..25.462 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=15368261.82..15368657.89 rows=158427 width=1942) (actual time=301266.829..301287.110 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=15174943.29..15354578.91 rows=158427 width=1942) (actual time=300068.588..301052.832 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Hash Join  (cost=15174792.93..15351854.12 rows=158427 width=684) (actual time=300066.288..300971.644 rows=155194 loops=1)
                     Hash Cond: (data.id = data_area.data_id)
                     ->  CTE Scan on data  (cost=0.00..6439.52 rows=321976 width=676) (actual time=26.290..313.842 rows=335130 loops=1)
                     ->  Hash  (cost=14857017.62..14857017.62 rows=25422025 width=8) (actual time=300028.260..300028.260 rows=26709939 loops=1)
                           Buckets: 4194304  Batches: 1  Memory Usage: 1043357kB
                           ->  Seq Scan on data_area  (cost=0.00..14857017.62 rows=25422025 width=8) (actual time=182921.056..291687.996 rows=26709939 loops=1)
                                 Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=108.49..108.49 rows=3349 width=1258) (actual time=2.256..2.256 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Seq Scan on area  (cost=0.00..108.49 rows=3349 width=1258) (actual time=0.007..0.666 rows=3349 loops=1)
 Total runtime: 301493.379 ms

Scan d'index (~ 3 secondes) ( sur expliqué.depesz.com )

Unique  (cost=17352256.47..17353067.50 rows=200 width=1942) (actual time=3603.303..3681.619 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6284.60..619979.56 rows=332340 width=297) (actual time=26.201..262.314 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6201.51 rows=335354 width=0) (actual time=25.381..25.381 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=17352256.47..17352661.98 rows=162206 width=1942) (actual time=3603.302..3623.113 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=1296.08..17338219.59 rows=162206 width=1942) (actual time=29.980..3375.921 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Nested Loop  (cost=0.00..17334287.66 rows=162206 width=684) (actual time=26.903..3268.674 rows=155194 loops=1)
                     ->  CTE Scan on data  (cost=0.00..6646.80 rows=332340 width=676) (actual time=26.205..421.858 rows=335130 loops=1)
                     ->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) (actual time=0.006..0.008 rows=0 loops=335130)
                           Index Cond: (data_id = data.id)
                           Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=1254.22..1254.22 rows=3349 width=1258) (actual time=3.057..3.057 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Index Scan using area_primary_key on area  (cost=0.00..1254.22 rows=3349 width=1258) (actual time=0.012..1.429 rows=3349 loops=1)
 Total runtime: 3706.630 ms

Structure de la table

Il s'agit de la structure de table pour la data_areatable. Je peux fournir les autres tableaux si besoin est.

CREATE TABLE data_area
(
  data_id integer NOT NULL,
  area_id integer NOT NULL,
  CONSTRAINT data_area_pkey PRIMARY KEY (data_id , area_id ),
  CONSTRAINT data_area_area_id_fk FOREIGN KEY (area_id)
      REFERENCES area (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT data_area_data_id_fk FOREIGN KEY (data_id)
      REFERENCES data (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
);

REQUETE

WITH data AS (
    SELECT * 
    FROM data 
    WHERE 
        datasetid IN (1) 
        AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') 
        AND depth BETWEEN 0 AND 99999
)
SELECT * 
FROM ( 
    SELECT DISTINCT ON (data.id) data.id, * 
    FROM 
        data, 
        data_area 
        LEFT JOIN area ON area_id = area.id 
    WHERE 
        data_id = data.id 
        AND area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
) as s;

Renvoie des 153812lignes. A set enable_seqscan= false;pour désactiver l'analyse séquentielle et obtenir le résultat de l'index.

J'ai essayé de faire un ANALYSEsur la base de données et d'augmenter les statistiques recueillies sur les colonnes utilisées dans la requête, mais rien ne semble aider.

Quelqu'un pourrait-il étendre et éclairer cela ou suggérer autre chose que je devrais essayer?


Cela m'aiderait si vous incluiez les requêtes qui ont généré chacun de ces plans d'exécution.
Mike Sherrill 'Cat Recall'

Une différence de 2 ordres de grandeur entre le nombre estimé de lignes et le nombre réel de lignes? Suis-je en train de lire ça?
Mike Sherrill 'Cat Recall'

@Catcall Ont ajouté la requête (un peu fondamentale pour pouvoir déterminer ce qui se passe). Lorsque vous vous référez aux lignes estimées, est-ce que le 200, puis son retour réel, 153812?
Mark Davidson

2
Oui, 200 vs 150k semble étrange à première vue. Y a-t-il une raison impérieuse de mélanger une jointure gauche avec un produit cartésien ( FROM data, data_area)? À première vue, l'utilisation de DISTINCT ON sans clause ORDER BY semble être une mauvaise idée.
Mike Sherrill 'Cat Recall'

Réponses:


8

Remarquez cette ligne:

->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) 
    (actual time=0.006..0.008 rows=0 loops=335130)

Si vous calculez le coût total, compte tenu des boucles, c'est le cas 52.3 * 335130 = 17527299. C'est plus grand que 14857017.62 pour l' seq_scanalternative. C'est pourquoi il n'utilise pas l'index.

L'optimiseur surestime donc le coût de l'analyse d'index. Je suppose que vos données sont triées sur l'index (soit en raison d'un index clusterisé ou de la façon dont elles ont été chargées) et / ou vous avez beaucoup de mémoire cache et / ou un joli disque rapide. Par conséquent, il y a peu d'E / S aléatoires en cours.

Vous devriez également vérifier l' correlationen pg_stats, qui est utilisé par l'optimiseur pour évaluer le regroupement lors du calcul du coût de l' indice, et enfin essayer de changer random_page_costet cpu_index_tuple_cost, pour correspondre à votre système.


À moins que je manque quelque chose, je pense que @jop signifiait 52.13, non 52.3, ce qui entraînerait 17470326.9 (toujours plus grand que le seq_scan)
BotNet

2

Votre CTE ne fait en fait rien d'autre que d'externaliser quelques WHEREconditions, la plupart d'entre elles étant équivalentes WHERE TRUE. Étant donné que les CTE sont généralement derrière une clôture d'optimisation (ce qui signifie qu'il est optimisé par lui-même), ils peuvent beaucoup aider avec certaines requêtes. Dans ce cas, cependant, je m'attendrais à l'effet inverse exact.

Ce que j'essaierais, c'est de réécrire la requête pour qu'elle soit aussi simple que possible:

SELECT d.id, * 
FROM 
    data d 
    JOIN data_area da ON da.data_id = d.id
    LEFT JOIN area a ON da.area_id = a.id 
WHERE 
    d.datasetid IN (1) 
    AND da.area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
    AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') -- this and the next condition don't do anything, I think
    AND depth BETWEEN 0 AND 99999
;

puis vérifiez si l'index est utilisé ou non. Il est encore très possible que vous n'ayez pas besoin de toutes les colonnes de sortie (au moins les deux colonnes de la table de jonction sont superflues).

Veuillez nous faire rapport et nous dire quelle version de PostgreSQL vous utilisez.


Merci pour votre suggestion, mes excuses pour ma réponse tardive à votre message, j'ai travaillé sur d'autres projets. Votre suggestion signifie en effet que la requête semble désormais utiliser de manière fiable l'index pour toutes les requêtes, mais je n'obtiens toujours pas les performances que j'attendrais avec. Je l' ai fait analyze sur une requête qui a beaucoup plus de données explain.depesz.com/s/1yu prend comme 4 minutes avec 95% du temps étant consacré à l'analyse INDEX.
Mark Davidson

J'ai oublié de mentionner que j'utilise la version 9.1.4
Mark Davidson

Fondamentalement, le balayage d'index est assez rapide, le problème est qu'il est répété plusieurs millions de fois. Qu'obtenez-vous si vous SET enable_nestloop=offavant d'exécuter la requête?
dezso

-1

Pour les adeptes, j'ai eu un problème similaire qui était comme

select * from table where bigint_column between x and y and mod(bigint_column, 10000) == z

Le problème était que ma bigint_column "entre x et y" avait un index, mais ma requête était essentiellement "toutes les lignes" dans cette table, donc elle n'utilisait pas l'index [car elle devait de toute façon analyser la table entière] mais faisait une analyse séquentielle seq_scan. Une solution pour moi était de créer un nouvel index pour le côté "mod" de l'équation, afin qu'il puisse l'utiliser sur une expression .


les downvoters se sentent libres de laisser des commentaires sur pourquoi :)
rogerdpack
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.