PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

L'index Postgresql sur l'expression xpath ne donne aucune accélération

Eh bien, au moins l'indice est utilisé. Cependant, vous obtenez un balayage d'index bitmap au lieu d'un balayage d'index normal, ce qui signifie que la fonction xpath() sera appelée de nombreuses fois.

Faisons une petite vérification :

CREATE TABLE foo ( id serial primary key, x xml, h hstore );
insert into foo (x,h) select XMLPARSE( CONTENT '<row  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  
   <object_id>2</object_id>  
   <pack_form_id>' || n || '</pack_form_id>  
   <prod_form_id>34</prod_form_id>
 </row>' ), 
('object_id=>2,prod_form_id=>34,pack_form_id=>'||n)::hstore 
FROM generate_series( 1,100000 ) n;

test=> EXPLAIN ANALYZE SELECT count(*) FROM foo;
                                                   QUERY PLAN                                                    
-----------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=4821.00..4821.01 rows=1 width=0) (actual time=24.694..24.694 rows=1 loops=1)
   ->  Seq Scan on foo  (cost=0.00..4571.00 rows=100000 width=0) (actual time=0.006..13.996 rows=100000 loops=1)
 Total runtime: 24.730 ms

test=> explain analyze select * from foo where (h->'pack_form_id')='123';
                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 Seq Scan on foo  (cost=0.00..5571.00 rows=500 width=68) (actual time=0.075..48.763 rows=1 loops=1)
   Filter: ((h -> 'pack_form_id'::text) = '123'::text)
 Total runtime: 36.808 ms

test=> explain analyze select * from foo where ((xpath('//pack_form_id/text()'::text, x))[1]::text) = '123';
                                              QUERY PLAN                                              
------------------------------------------------------------------------------------------------------
 Seq Scan on foo  (cost=0.00..5071.00 rows=500 width=68) (actual time=4.271..3368.838 rows=1 loops=1)
   Filter: (((xpath('//pack_form_id/text()'::text, x, '{}'::text[]))[1])::text = '123'::text)
 Total runtime: 3368.865 ms

Comme nous pouvons le voir,

  • le balayage de toute la table avec count(*) prend 25 ms
  • extraire une clé/valeur d'un hstore ajoute un petit coût supplémentaire, environ 0,12 µs/ligne
  • extraire une clé/valeur d'un xml à l'aide de xpath ajoute un coût énorme, environ 33 µs/ligne

Conclusion :

  • xml est lent (mais tout le monde le sait)
  • si vous voulez mettre un magasin clé/valeur flexible dans une colonne, utilisez hstore

De plus, puisque vos données xml sont assez volumineuses, elles seront grillées (compressées et stockées hors de la table principale). Cela rend les lignes de la table principale beaucoup plus petites, donc plus de lignes par page, ce qui réduit l'efficacité des analyses bitmap puisque toutes les lignes d'une page doivent être revérifiées.

Vous pouvez cependant résoudre ce problème. Pour une raison quelconque, la fonction xpath() (qui est très lente, car elle gère xml) a le même coût (1 unité) que, par exemple, l'opérateur entier "+"...

update pg_proc set procost=1000 where proname='xpath';

Vous devrez peut-être modifier la valeur du coût. Lorsqu'il reçoit les bonnes informations, le planificateur sait que xpath est lent et évitera un balayage d'index bitmap, en utilisant un balayage d'index à la place, qui n'a pas besoin de revérifier la condition pour toutes les lignes d'une page.

Notez que cela ne résout pas le problème des estimations de lignes. Comme vous ne pouvez pas ANALYSER l'intérieur du xml (ou hstore), vous obtenez des estimations par défaut pour le nombre de lignes (ici, 500). Ainsi, le planificateur peut se tromper complètement et choisir un plan catastrophique si des jointures sont impliquées. La seule solution à cela est d'utiliser des colonnes appropriées.