sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgresql-index på xpath-uttryck ger ingen hastighet

Nåväl, åtminstone används indexet. Du får dock en bitmappsindexsökning istället för en vanlig indexskanning, vilket betyder att xpath()-funktionen kommer att anropas många gånger.

Låt oss göra en liten kontroll :

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

Som vi kan se,

  • att skanna hela tabellen med count(*) tar 25 ms
  • att extrahera en nyckel/värde från en hstore tillför en liten extra kostnad, cirka 0,12 µs/rad
  • att extrahera en nyckel/värde från en xml med xpath tillför en enorm kostnad, cirka 33 µs/rad

Slutsatser:

  • xml är långsam (men det vet alla)
  • om du vill lägga ett flexibelt nyckel-/värdelager i en kolumn, använd hstore

Eftersom din xml-data är ganska stor kommer den att rostas (komprimeras och lagras utanför huvudtabellen). Detta gör raderna i huvudtabellen mycket mindre, därav fler rader per sida, vilket minskar effektiviteten av bitmappsskanningar eftersom alla rader på en sida måste kontrolleras igen.

Du kan fixa detta dock. Av någon anledning har funktionen xpath() (som är väldigt långsam eftersom den hanterar xml) samma kostnad (1 enhet) som heltalsoperatorn "+"...

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

Du kan behöva justera kostnadsvärdet. När den får rätt information vet planeraren att xpath är långsam och kommer att undvika en bitmappsindexskanning, med hjälp av en indexskanning istället, som inte behöver kontrollera villkoret för alla rader på en sida igen.

Observera att detta inte löser problemet med raduppskattningar. Eftersom du inte kan ANALYSE insidan av xml (eller hstore) får du standarduppskattningar för antalet rader (här, 500). Så, planeraren kan ha helt fel och välja en katastrofal plan om några anslutningar är inblandade. Den enda lösningen på detta är att använda rätt kolumner.




  1. 10 ovanliga Microsoft Access 2019-tips

  2. MySQL - Visa alla permutationer?

  3. Det finns inga primära eller kandidatnycklar i den refererade tabellen som matchar referenskolumnlistan i den främmande nyckeln

  4. många-till-många-relation OrderBy - Laravel-frågebyggare