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.