Bueno, al menos se usa el índice. Sin embargo, obtiene un escaneo de índice de mapa de bits en lugar de un escaneo de índice normal, lo que significa que la función xpath() se llamará muchas veces.
Hagamos una pequeña verificación:
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,
- escanear toda la tabla con count(*) tarda 25 ms
- extraer una clave/valor de un hstore agrega un pequeño costo adicional, alrededor de 0,12 µs/fila
- extraer una clave/valor de un xml usando xpath agrega un costo enorme, alrededor de 33 µs/fila
Conclusiones:
- xml es lento (pero todo el mundo lo sabe)
- si desea colocar un almacén de clave/valor flexible en una columna, use hstore
Además, dado que sus datos xml son bastante grandes, se tostarán (comprimirán y almacenarán fuera de la tabla principal). Esto hace que las filas de la tabla principal sean mucho más pequeñas, por lo tanto, más filas por página, lo que reduce la eficiencia de los escaneos de mapas de bits, ya que todas las filas de una página deben volver a verificarse.
Sin embargo, puedes arreglar esto. Por alguna razón, la función xpath() (que es muy lenta, ya que maneja xml) tiene el mismo costo (1 unidad) que, por ejemplo, el operador entero "+"...
update pg_proc set procost=1000 where proname='xpath';
Es posible que deba modificar el valor del costo. Cuando se le proporciona la información correcta, el planificador sabe que xpath es lento y evitará un escaneo de índice de mapa de bits, utilizando en su lugar un escaneo de índice, que no necesita volver a verificar la condición de todas las filas en una página.
Tenga en cuenta que esto no resuelve el problema de estimaciones de fila. Como no puede ANALIZAR el interior del xml (o hstore), obtiene estimaciones predeterminadas para el número de filas (aquí, 500). Entonces, el planificador puede estar completamente equivocado y elegir un plan catastrófico si hay algunas uniones involucradas. La única solución a esto es usar las columnas adecuadas.