PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

O índice Postgresql na expressão xpath não acelera


Bem, pelo menos o índice é usado. Você obtém uma varredura de índice de bitmap em vez de uma varredura de índice normal, o que significa que a função xpath() será chamada muitas vezes.

Vamos fazer uma pequena verificação:
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

Como podemos ver,
  • verificar toda a tabela com count(*) leva 25 ms
  • extrair uma chave/valor de um hstore adiciona um pequeno custo extra, cerca de 0,12 µs/linha
  • extrair uma chave/valor de um xml usando xpath adiciona um custo enorme, cerca de 33 µs/linha

Conclusões:
  • xml é lento (mas todos sabem disso)
  • se você quiser colocar um armazenamento de chave/valor flexível em uma coluna, use hstore

Além disso, como seus dados xml são muito grandes, eles serão torrados (compactados e armazenados fora da tabela principal). Isso torna as linhas na tabela principal muito menores, portanto, mais linhas por página, o que reduz a eficiência das varreduras de bitmap, pois todas as linhas de uma página precisam ser verificadas novamente.

Você pode corrigir isso embora. Por alguma razão a função xpath() (que é muito lenta, pois lida com xml) tem o mesmo custo (1 unidade) que digamos, o operador inteiro "+"...
update pg_proc set procost=1000 where proname='xpath';

Pode ser necessário ajustar o valor de custo. Quando recebe as informações corretas, o planejador sabe que o xpath é lento e evitará uma varredura de índice de bitmap, usando uma varredura de índice, que não precisa verificar novamente a condição de todas as linhas em uma página.

Observe que isso não resolve o problema de estimativas de linha. Como você não pode ANALISAR o interior do xml (ou hstore), você obtém estimativas padrão para o número de linhas (aqui, 500). Assim, o planejador pode estar completamente errado e escolher um plano catastrófico se algumas junções estiverem envolvidas. A única solução para isso é usar colunas adequadas.