2010-02-25 7 views
19

J'ai passé plus d'une heure aujourd'hui à m'interroger sur un plan de requête que je ne pouvais pas comprendre. La requête était un UDPATE et elle ne fonctionnerait pas du tout. Totalement dans l'impasse: pg_locks a montré qu'il n'attendait rien non plus. Maintenant, je ne me considère pas comme le meilleur ou le pire gars lecteur de plan de requête, mais je trouve celui-ci exceptionnellement difficile. Je me demande comment peut-on lire ces? Y a-t-il une méthodologie que les as Pg suivent afin de localiser l'erreur?Comment puis-je "penser mieux" en lisant un plan de requête PostgreSQL? (Exemple ci-joint)

Je prévois de poser une autre question sur la façon de contourner ce problème, mais en ce moment je parle spécifiquement sur comment lire ces types de plans. Veuillez ne pas pointer vers un didacticiel générique à moins qu'il ne traite spécifiquement ce problème, mis en évidence ci-dessous le plan de requête.

          QUERY PLAN           
-------------------------------------------------------------------------------------------- 
Nested Loop Anti Join (cost=47680.88..169413.12 rows=1 width=77) 
    Join Filter: ((co.fkey_style = v.chrome_styleid) AND (co.name = o.name)) 
    -> Nested Loop (cost=5301.58..31738.10 rows=1 width=81) 
     -> Hash Join (cost=5301.58..29722.32 rows=229 width=40) 
       Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text)) 
       -> Seq Scan on options io (cost=0.00..20223.32 rows=23004 width=36) 
        Filter: (name IS NULL) 
       -> Hash (cost=4547.33..4547.33 rows=36150 width=24) 
        -> Seq Scan on vehicles iv (cost=0.00..4547.33 rows=36150 width=24) 
          Filter: (date_sold IS NULL) 
     -> Index Scan using options_pkey on options co (cost=0.00..8.79 rows=1 width=49) 
       Index Cond: ((co.fkey_style = iv.chrome_styleid) AND (co.code = io.code)) 
    -> Hash Join (cost=42379.30..137424.09 rows=16729 width=26) 
     Hash Cond: ((v.lot_id = o.lot_id) AND ((v.vin)::text = (o.vin)::text)) 
     -> Seq Scan on vehicles v (cost=0.00..4547.33 rows=65233 width=24) 
     -> Hash (cost=20223.32..20223.32 rows=931332 width=44) 
       -> Seq Scan on options o (cost=0.00..20223.32 rows=931332 width=44) 
(17 rows) 

Le problème avec ce plan de requête - je crois que je comprends - est probablement mieux dit par RhodiumToad (il est certainement mieux à cela, donc je vais parier sur son explication étant mieux) de irc://irc.freenode.net/#postgresql:

oh, ce plan est potentiellement disasterous le problème avec ce plan est qu'il est en cours d'exécution d'un hashjoin extrêmement coûteux pour chaque ligne le problème est rows = 1 estimation de l'autre rejoindre et le planificateur pense qu'il est autorisé à mettre un énorme demande coûteuse dans le chemin interne d'un nestloop où le chemin externe est estimé pour retourner une seule ligne. puisque, évidemment, selon l'estimation du planificateur, la partie la plus chère ne sera exécutée qu'une fois, mais cela a une tendance évidente à vraiment gâcher en pratique le problème est que le planificateur estime ses propres estimations idéalement, le planificateur doit connaître le différence entre « estimée à revenir 1 rang » et « pas possible de revenir plus de 1 ligne » mais pas du tout clair comment incorporer dans le code existant

il poursuit en disant:

il peut affecter n'importe quelle jointure, mais se joint généralement contre les sous-requêtes sont les plus susceptibles

Maintenant, quand je lis ce plan, la première chose que j'ai remarqué était le Nested Loop Anti Join, cela a eu un coût de 169,413 (je vais en tenir à des limites supérieures). Cet anti-joint se décompose en résultat Nested Loop au coût de 31,738, et le résultat d'un Hash Join au coût de 137,424. Maintenant, le 137,424, est beaucoup plus grand que 31,738 donc je savais que le problème était le Hash Join. Ensuite, je passe à EXPLAIN ANALYZE le segment de jointure de hachage en dehors de la requête. Il a exécuté en 7 secondes. Je me suis assuré qu'il y avait des index sur (lot_id, vin), et (co.code, et v.code) - il y avait. J'ai désactivé seq_scan et hashjoin individuellement et remarquez une augmentation de la vitesse de moins de 2 secondes. Pas assez proche pour expliquer pourquoi il ne progressait pas après une heure.

Mais, après tout cela, je me trompe totalement! Oui, c'était la partie la plus lente de la requête, mais parce que le bit rows="1" (je présume qu'il était sur le Nested Loop Anti Join). Y a-t-il un tutoriel qui m'aidera à identifier ces types de problèmes? Ici, c'est un bug (manque de capacité) dans le planificateur d'estimation erronée du nombre de lignes?Comment suis-je censé lire ceci pour arriver à la même conclusion? RhodiumToad fait?

Est-ce simplement rows="1" qui est censé me déclencher ceci comprendre?

J'ai couru VACUUM FULL ANALYZE sur toutes les tables impliquées, et c'est Postgresql 8.4.

Répondre

24

Voir à travers les problèmes comme celui-ci exige une certaine expérience où les choses peuvent mal tourner. Mais pour trouver des problèmes dans les plans de requête, essayez de valider le plan produit de l'intérieur, vérifiez si le nombre d'estimations de lignes est sain et si les estimations de coûts correspondent au temps passé. Btw. les deux estimations de coûts ne sont pas inférieures et supérieures, le premier est le coût estimé pour produire la première ligne de production, le deuxième nombre est le coût total estimé, voir explain documentation pour les détails, il ya aussi quelques planner documentation disponibles. Cela permet également de savoir comment fonctionnent les différentes méthodes d'accès. Comme point de départ Wikipedia a des informations sur nested loop, hash et merge joins.

Dans votre exemple, vous commencer par:

  -> Seq Scan on options io (cost=0.00..20223.32 rows=23004 width=36) 
       Filter: (name IS NULL) 

Run EXPLAIN ANALYZE SELECT * FROM options WHERE name IS NULL; et voir si les lignes renvoyées correspond à l'estimation. Un facteur de 2 n'est généralement pas un problème, vous essayez de repérer les différences d'ordre de grandeur.

Puis voir EXPLAIN ANALYZE SELECT * FROM vehicles WHERE date_sold IS NULL; rendements attendus nombre de lignes.

ensuite remonter d'un niveau à la jointure de hachage:

 -> Hash Join (cost=5301.58..29722.32 rows=229 width=40) 
      Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text)) 

Voir si EXPLAIN ANALYZE SELECT * FROM vehicles AS iv INNER JOIN options io ON (io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text) WHERE iv.date_sold IS NULL AND io.name IS NULL; résultats dans 229 lignes.

Remonter d'un niveau plus ajoute INNER JOIN options co ON (co.fkey_style = iv.chrome_styleid) AND (co.code = io.code) et devrait revenir qu'une seule ligne. C'est probablement là où le problème est dû au fait que si le nombre réel de lignes passe de 1 à 100, l'estimation du coût total de traversée de la boucle interne de la boucle imbriquée contient un facteur 100.

le planificateur est probablement qu'il s'attend à ce que les deux prédicats pour se joindre à co soient indépendants les uns des autres et multiplient leurs sélectivités. Alors qu'en réalité ils peuvent être fortement corrélés et que la sélectivité est plus proche de MIN (s1, s2) que de s1 * s2.

+0

C'est une bonne réponse, mais de laquelle parlez-vous lorsque vous dites "se joindre"? Je crois que "RhodiumToads" explication du problème, car il semble exact? Est-ce que vous expliquez la même chose ou quelque chose de différent? –

+0

Même chose. Rejoindre 'co' est le' Index Scan en utilisant options_pkey sur le nœud interne des options co' de la jointure de la boucle imbriquée. Il a deux conditions que le planificateur pense probablement irréaliste se traduira par une ligne de sortie.Si vous essayez d'exécuter cette requête et voyez combien de lignes elle retourne vraiment, vous pouvez vérifier si c'est le cas. Les mauvaises estimations pour les prédicats corrélés sont un problème connu. Il y a quelques discussions à ce sujet sur la liste des performances: http://archives.postgresql.org/pgsql-performance/2009-06/msg00055.php –

2

Avez-vous ANALYSÉ les tables? Et qu'est-ce que pg_stats a à dire à propos de ces tables? Le plan de requête est basé sur les statistiques, celles-ci doivent être correctes. Et quelle version utilisez-vous? 8.4?

Les coûts peuvent être calculés en utilisant les statistiques, le montant de relpages, nombre de lignes et les paramètres dans postgresql.conf pour les Constantes de coût du planificateur.

work_mem est également impliqué, il pourrait être trop bas et forcer le planificateur à faire un seqscan, pour tuer la performance ...

+0

Je ne lancez 'VACUUM FULL ANALYZE' sur toutes les tables impliquées, et c'est Postgresql 8.4. –