Index non utilisé, mais influençant la requête


8

J'ai une table PostgreSQL 9.3 avec quelques chiffres et quelques données supplémentaires:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Cette table compte actuellement environ 10 millions d'enregistrements et occupe 1 Go d'espace disque. myidne sont pas consécutifs.

Je veux calculer le nombre de lignes dans chaque bloc de 100 000 nombres consécutifs:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Cela renvoie environ 3500 lignes.

J'ai remarqué que l'existence d'un certain index accélère considérablement cette requête même si le plan de requête ne le mentionne pas du tout. Le plan de requête sans l'index:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

L'index:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

Le nouveau plan de requête:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Ainsi, les plans de requête et les temps d'exécution diffèrent considérablement (près de trois fois) mais ne mentionnent pas l'index. Ce comportement est parfaitement reproductible sur ma machine de développement: j'ai effectué plusieurs cycles de suppression de l'index, de test de la requête plusieurs fois, de recréation de l'index, de nouveau de tester la requête plusieurs fois. Qu'est-ce qu'il se passe ici?


Je ne suis pas un expert dans l'analyse des plans de requête de Postgres mais je suppose que l'index est utilisé pour la HashAggregateméthode (et aucun tri n'est requis), donc vous obtenez de meilleures performances. Pourquoi l'indice n'est pas mentionné dans le plan, je n'en ai aucune idée.
ypercubeᵀᴹ

La sortie du plan change-t-elle si vous activez le mode détaillé à l'aide de explain (analyze true, verbose true) ...:?
a_horse_with_no_name

Ce serait génial si vous pouviez résumer celui-ci dans un cas de test autonome. Cela semble vraiment étrange.
Craig Ringer

@a_horse_with_no_name: Oui, cela change - j'ai remplacé les plans de requête par ceux détaillés dans la question. Mais ce plan de requête ne mentionne toujours pas l'index du tout.
liori

S'il y a plus de statistiques disponibles (en particulier cardinalité et éventuellement valeurs min / max) sur la colonne id avec l'index que sans, cela pourrait changer le groupe de l'optimiseur par sélection de méthode, même s'il ne finit pas du tout par utiliser l'index . (Je ne connais pas du tout l'optimiseur et les statistiques de postgres, donc aucune idée si cela pourrait être le cas ou non.)
Mat

Réponses:


3

VACUUM ANALYZEfait la différence dans votre exemple. De plus, comme @jjanes l'a fourni , les statistiques supplémentaires pour l'index fonctionnel. Par documentation:

pg_statisticstocke également des données statistiques sur les valeurs des expressions d'index. Celles-ci sont décrites comme s'il s'agissait de colonnes de données réelles; en particulier, fait starelidréférence à l'index. Cependant, aucune entrée n'est faite pour une colonne d'index de non-expression ordinaire, car elle serait redondante avec l'entrée de la colonne de table sous-jacente.

Cependant, la création de l'index ne provoque pas à elle seule la collecte de statistiques par Postgres. Essayer:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Ne renvoie rien tant que vous n'avez pas exécuté votre premier ANALYZE(ou VACUUM ANALYZE, ou que le démon autovacuum entre en action).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Vous verrez maintenant des statistiques supplémentaires.

Étant donné que la table entière doit être lue de toute façon, Postgres va utiliser un scan séquentiel à moins qu'il ne s'attende à ce que le calcul myid/100000soit assez coûteux pour basculer, ce qui n'est pas le cas.

Votre seule autre chance serait une analyse d'index uniquement si l'index est beaucoup plus petit que la table - et les conditions préalables à une analyse d'index uniquement sont remplies. Détails dans le wiki Postgres et dans le manuel .

Tant que cet indice fonctionnel n'est pas utilisé, l'avantage collatéral des statistiques ajoutées est modéré. Si la table était en lecture seule, le coût serait faible - mais là encore, nous verrions probablement une analyse indexée immédiatement.

Vous pouvez peut-être également obtenir de meilleurs plans de requête en définissant un objectif de statistiques plus élevé pour mytable.myid. Cela n'entraînerait qu'un coût mineur. Plus:


Merci pour cette explication, elle est très utile pour comprendre le problème. Dans mon cas, j'aurai probablement besoin d'une myid/100000 BETWEEN somevalue AND othervaluecondition supplémentaire , donc l'index sera utilisé dans le plan de requête de toute façon - je viens de poser cette question parce que je ne comprenais pas pourquoi l'index est utile dans le cas de la table entière.
liori

@liori: vous pouvez couvrir cela avec WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(pensez aux effets d'arrondi en fonction de vos types), et vous avez probablement déjà un index simple myid, vous pouvez donc vous passer d'un index spécialisé supplémentaire. Pourrait être plus efficace.
Erwin Brandstetter

6

Lorsque vous créez un index d'expression, PostgreSQL collecte des statistiques sur cette expression. Grâce à ces statistiques, il dispose désormais d'une estimation précise du nombre de lignes agrégées que la requête renverra, ce qui l'amène à faire un meilleur choix de plan.

Plus précisément dans ce cas, sans ces statistiques supplémentaires, il pensait que la table de hachage serait trop grande pour tenir dans work_mem, donc il n'a pas choisi cette méthode.


Je pense que le planificateur ne tient pas compte de la valeur de work_mem. Si vous l'avez élevé de sorte que le tri tienne dans la mémoire, il utiliserait toujours le même plan. Permettez-moi de noter ici que la différence de temps (la plupart du temps) provient du type de disque externe.
dezso

1
@dezso Que se passe-t-il si vous doublez ou triplez expérimentalement la valeur de work_mem qui était nécessaire pour adapter le tri en mémoire? Le tri et le hachage ont des estimations de frais généraux différentes, et les estimations elles-mêmes ne sont pas très précises. De plus, quelle version mineure de 9.3 utilisez-vous?
jjanes
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.