web-dev-qa-db-fra.com

Comment comprendre un EXPLAIN ANALYSE

Je ne connais pas très bien les résultats EXPLAIN ANALYZE. Le problème est que mes requêtes sont trop lentes. J'ai essayé de lire comment interpréter les résultats d'une requête d'explication, mais je ne sais toujours pas ce que je devrais rechercher et ce qui pourrait ne pas être correct. J'ai l'impression qu'il y a une grosse lumière rouge qui clignote quelque part, je ne la vois tout simplement pas.

Donc, la requête est assez simple, elle ressemble à ceci:

EXPLAIN ANALYZE SELECT "cars".* FROM "cars" WHERE "cars"."sales_state" = 'onsale' AND "cars"."brand" = 'BMW' AND "cars"."model_name" = '318i' AND "cars"."has_auto_gear" = TRUE  LIMIT 25 OFFSET 0

Et le résultat ressemble à ceci:

Limit  (cost=0.00..161.07 rows=25 width=1245) (actual time=35.232..38.694 rows=25 loops=1)
  ->  Index Scan using index_cars_onsale_on_brand_and_model_name on cars  (cost=0.00..1179.06 rows=183 width=1245) (actual time=35.228..38.652 rows=25 loops=1)
        Index Cond: (((brand)::text = 'BMW'::text) AND ((model_name)::text = '318i'::text))
        Filter: has_auto_gear"
Total runtime: 38.845 ms

Un peu de fond: Je suis sur PostgreSQL 9.1.6, fonctionnant sur des bases de données dédiées Herokus. Ma base de données a environ 7,5 Go de RAM, la table cars contient 3,1 millions de lignes et environ 2,0 millions de lignes ont sale_state = 'onsale'. Le tableau a 170 colonnes. L'index qu'il utilise ressemble à ceci:

CREATE INDEX index_cars_onsale_on_brand_and_model_name
  ON cars
  USING btree
  (brand COLLATE pg_catalog."default" , model_name COLLATE pg_catalog."default" )
  WHERE sales_state::text = 'onsale'::text;

Quelqu'un a vu un gros problème évident?

MODIFIER:

SELECT pg_relation_size('cars'), pg_total_relation_size('cars');

pg_relation_size: 2058444800 pg_total_relation_size: 4900126720

SELECT pg_relation_size('index_cars_onsale_on_brand_and_model_name');

pg_relation_size: 46301184

SELECT avg(pg_column_size(cars)) FROM cars limit 5000;

en moyenne: 636.9732567210792995

SANS LA LIMITE:

EXPLAIN ANALYZE SELECT "cars".* FROM "cars" WHERE "cars"."sales_state" = 'onsale' AND "cars"."brand" = 'BMW' AND "cars"."model_name" = '318i' AND "cars"."has_auto_gear" = TRUE

Bitmap Heap Scan on cars  (cost=12.54..1156.95 rows=183 width=4) (actual time=17.067..55.198 rows=2096 loops=1)
  Recheck Cond: (((brand)::text = 'BMW'::text) AND ((model_name)::text = '318i'::text) AND ((sales_state)::text = 'onsale'::text))
  Filter: has_auto_gear
  ->  Bitmap Index Scan on index_cars_onsale_on_brand_and_model_name  (cost=0.00..12.54 rows=585 width=0) (actual time=15.211..15.211 rows=7411 loops=1)"
        Index Cond: (((brand)::text = 'BMW'::text) AND ((model_name)::text = '318i'::text))
Total runtime: 56.851 ms
31
Niels Kristian

Bien que cela ne soit pas aussi utile pour un plan simple comme celui-ci, http://explain.depesz.com est vraiment utile. Voir http://explain.depesz.com/s/t4fi . Notez l'onglet "stats" et le menu "options".

Choses à noter sur ce plan: 

  • Le nombre estimé de lignes (183) est raisonnablement comparable au nombre réel de lignes (25). Ce n'est pas des centaines de fois plus, ni 1. Vous êtes plus intéressé par les ordres de grandeur en ce qui concerne les estimations de nombre de lignes, ou les problèmes "1 vs pas 1". (Vous n’avez même pas besoin d’exactitude "suffisamment proche pour le travail gouvernemental" - "assez proche pour la comptabilité contractuelle militaire" fera l'affaire). L'estimation de la sélectivité et les statistiques semblent raisonnables.

  • Il utilise l'index partiel à deux colonnes fourni (index scan using index_cars_onsale_on_brand_and_model_name), il correspond donc à la condition d'index partiel. Vous pouvez voir cela dans le Filter: has_auto_gear. La condition de recherche d'index est également affichée.

  • Les performances de la requête semblent raisonnables étant donné que le nombre de lignes de la table signifie que l'index est assez volumineux, notamment s'il est composé de deux colonnes. Les lignes correspondantes seront dispersées, il est donc probable que chaque ligne nécessite une lecture distincte de la page.

Je vois rien de mal ici. Cependant, cette requête bénéficiera grandement des analyses de type index uniquement de PostgreSQL 9.2.

Il est possible qu'il y ait un peu de tableau ici, mais étant donné l'index à 2 colonnes et le nombre important de lignes, le temps de réponse n'est pas totalement déraisonnable, en particulier pour un tableau de 170 colonnes (!!) qui est susceptible de contenir relativement peu de multiplets dans chaque page. Si vous pouvez vous permettre un temps d'arrêt, essayez VACUUM FULL pour réorganiser la table et reconstruire l'index. Cela verrouille exclusivement la table pendant un certain temps pendant qu'il la reconstruit. Si vous ne pouvez pas vous permettre le temps d'arrêt, voir pg_reorg et/ou CREATE INDEX CONCURRENTLY et ALTER INDEX ... RENAME TO.

Vous pouvez trouver EXPLAIN (ANALYZE, BUFFERS, VERBOSE) plus informatif parfois, car il peut afficher les accès au tampon, etc.

Une option permettant d'accélérer cette requête (bien que le risque de ralentir quelque peu les autres requêtes) est de partitionner la table sur brand et d'activer constraint_exclusion. Voir partitionnement .

27
Craig Ringer

Eh bien ... la première chose que je puisse vous dire, c'est que votre base de données s'attend à obtenir (d'après les statistiques) 183 lignes. En réalité, il y a 25 rangées. Bien que cela ne soit probablement pas très pertinent dans ce cas (c’est-à-dire avec ces petites quantités et aucune opération lourde, n’aurez pas à vous inquiéter de l’estimation erronée).

Un problème plus important (à mon humble avis) est qu’une simple recherche d’index sur 25 lignes prend 35 ms. Cela semble un peu beaucoup. La base de données est-elle suffisamment lourde pour avoir au moins tous les index en mémoire? Ce n’est pas excessif, c’est un peu lent pour moi.

En ce qui concerne vos explications, je vous recommanderais d’expliquer expliquer.depesz.com: http://explain.depesz.com/s/sA6

0
Wolph