PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

L'indice Postgresql sull'espressione xpath non aumenta la velocità

Bene, almeno viene utilizzato l'indice. Tuttavia, ottieni una scansione dell'indice bitmap invece di una normale scansione dell'indice, il che significa che la funzione xpath() verrà chiamata molte volte.

Facciamo un piccolo controllo :

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

Come possiamo vedere,

  • la scansione dell'intera tabella con count(*) richiede 25 ms
  • l'estrazione di una chiave/valore da un hstore comporta un piccolo costo aggiuntivo, circa 0,12 µs/riga
  • l'estrazione di una chiave/valore da un xml utilizzando xpath comporta un costo enorme, circa 33 µs/riga

Conclusioni :

  • xml è lento (ma lo sanno tutti)
  • se vuoi inserire un archivio chiave/valore flessibile in una colonna, usa hstore

Inoltre, poiché i tuoi dati xml sono piuttosto grandi, verranno tostati (compressi e archiviati fuori dalla tabella principale). Ciò rende le righe nella tabella principale molto più piccole, quindi più righe per pagina, il che riduce l'efficienza delle scansioni bitmap poiché tutte le righe di una pagina devono essere ricontrollate.

Puoi risolvere questo problema però. Per qualche motivo la funzione xpath() (che è molto lenta, poiché gestisce xml) ha lo stesso costo (1 unità) dell'operatore intero "+"...

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

Potrebbe essere necessario modificare il valore del costo. Quando vengono fornite le informazioni corrette, il pianificatore sa che xpath è lento ed eviterà una scansione dell'indice bitmap, utilizzando invece una scansione dell'indice, che non ha bisogno di ricontrollare la condizione per tutte le righe di una pagina.

Si noti che questo non risolve il problema delle stime di riga. Dal momento che non puoi ANALIZZARE l'interno di xml (o hstore), ottieni stime predefinite per il numero di righe (qui, 500). Quindi, il pianificatore potrebbe essere completamente sbagliato e scegliere un piano catastrofico se sono coinvolti alcuni join. L'unica soluzione è utilizzare le colonne appropriate.