2017-02-13 8 views
10

Przeczytałem o indeksach funkcjonalnych i skanach zindeksowanych w dokumencie/wiki opublikowanym przez Postgres.Postgres Indeks skanowania 9,6 tylko na indeks funkcjonalny jest logicznie możliwy, ale nie został wykonany

teraz mam zapytanie jak:

SELECT(xpath('/document/uuid/text()', xmldata))[1]::text, 
     (xpath('/document/title/text()', xmldata))[1]::text 
FROM xmltable 
WHERE(xpath('/document/uuid/text()', xmldata))[1]::text = 'some-uuid-xxxx-xxxx' 

i indeksie:

CREATE INDEX idx_covering_index on xmltable using btree (
    ((xpath('/document/uuid/text()', xmldata))[1]::text),  
    ((xpath('/document/title/text()', xmldata))[1]::text) 
) 

Indeks ten, patrząc na to logicznie, wskaźnik pokrycia i powinna umożliwić indeks-Only-skan , ponieważ wszystkie żądane wartości są zawarte w indeksie (uuid i title)

Teraz wiem, że Postgres rozpoznaje tylko indeksy obejmujące indeksy funkcjonalne, jeśli kolumny używane w functio N- połączenia są również zawarte

np .:

SELECT to_upper(column1) from table where id >10 

1) nie może być pokryte przez tę index:

CREATE INDEX idx_covering_index on xmltable using btree (id, to_upper(column1)); 

2), ale może być pokryta tego:

CREATE INDEX idx_covering_index on xmltable using btree (column1, id, to_upper(column1)); 

co prowadzi do skanowania tylko do indeksu.

Gdybym teraz spróbuj to z mojej konfiguracji XML:

CREATE INDEX idx_covering_index on xmltable using btree (xmldata, 
    ((xpath('/document/uuid/text()', xmldata))[1]::text),  
    ((xpath('/document/title/text()', xmldata))[1]::text) 
) 

pojawia się błąd:

data type xml has no default operator class for access method "btree"

uczciwą tyle, niestety zwykle używany "text_ops" lub "text_pattern_ops" nie akceptują "xml" jako input - w ten sposób renderując mój indeks - choć obejmowałby on wszystkie wartości - nie może obsługiwać skanów tylko z indeksem.

Czy można to obsłużyć w sposób umożliwiający skanowanie tylko do indeksu?

@ Edit1:

wiem, że postgres nie może użyć indeksu widziany w 1) pokrycie indeksu, ale można użyć indeksu jak 2)

Próbowałem też z bardzo prostych tabel w celu sprawdzenia tego zachowania , i ja też pamiętam, że to przeczytałem - ale nie mogę o tym pamiętać.

create table test (
    id serial primary key, 
    quote text 
) 



insert into test (number, quote) values ('I do not know any clever quotes'); 
insert into test (number, quote) values ('I am sorry'); 



CREATE INDEX idx_test_functional on test using btree ((regexp_replace(quote, '^I ', 'BillDoor '))); 
set enable_seqscan = off; 

analyze test; 

explain select quote from test where regexp_replace(quote, '^I ', 'BillDoor ') = 'BillDoor do not know any clever quotes' 

--> "Index Scan using idx_test_functional on test (cost=0.13..8.15 rows=1 width=27)" 

drop index idx_test_functional; 
CREATE INDEX idx_test_functional on test using btree (quote, (regexp_replace(quote, '^I ', 'BillDoor '))); 

analyze test; 

explain select quote from test where regexp_replace(quote, '^I ', 'BillDoor ') = 'BillDoor do not know any clever quotes' 

--> "Index Only Scan using idx_test_functional on test (cost=0.13..12.17 rows=1 width=27)" 

@ Edit2:

pełną definicję tabeli xmltable:

id serial primary key (clustered), 
xmldata xml (only data used to filter queries) 
history xml (never queried or read, just kept in case of legal inquiry) 
fileinfo text (seldom quieried, sometimes retrieved) 
"timestamp" timestamp (mainly for legal inquiries too) 

tabeli zawiera ok .: 500.000 Rekordy, xmldata ma rozmiar od 350 do 800 bajtów, historia jest znacznie większa, ale rzadko pobierana i nigdy nie jest używana w filtrach

Dla pewności, że uzyskałem prawdziwe wyniki, zawsze po uruchomieniu lub porzuceniu uruchomiłem analyze xmltable indeks

pełny plan wykonania dla zapytania:

explain analyze select (xpath('/document/uuid/text()', d.xmldata))[1]::text as uuid 
from xmltable as d 
where 
(xpath('/document/uuid/text()', d.xmldata))[1]::text = 'some-uuid-xxxx-xxxx' and (xpath('/document/genre/text()', d.xmldata))[1]::text = 'bio' 

objętym niniejszym indizies:

create index idx_genre on xmltable using btree (((xpath('/document/genre/text()', xmldata))[1]::text)); 

create index idx_uuid on xmltable using btree (((xpath('/document/uuid/text()', xmldata))[1]::text)); 

create index idx_uuid_genre on xmltable using btree (((xpath('/document/uuid/text()', xmldata))[1]::text), ((xpath('/document/genre/text()', xmldata))[1]::text)); 

pierwszy lea DS:

"Index Scan using idx_genre on xmldata d (cost=0.42..6303.05 rows=18154 width=32)" 
" Index Cond: (((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text)" 
" Filter: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)" 

sprawiedliwe tyle myślałem, po prostu do testowania będę zmuszać go używać - w moim umyśle - indeks obejmujący:

drop index idx_uuid; 
drop index idx_genre; 

i teraz uzyskać:

"Bitmap Heap Scan on xmltable d (cost=551.13..16025.51 rows=18216 width=32)" 
" Recheck Cond: ((((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text) AND (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text))" 
" -> Bitmap Index Scan on idx_uuid_genre (cost=0.00..546.58 rows=18216 width=0)" 
"  Index Cond: ((((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text) AND (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text))" 

Próbowałem także przełączać pozycje uuid i gatunek w indeksie, ten sam plan wykonania.

+0

'Teraz już wiem, że postgres rozpoznaje tylko ukryte indizje na funkcjonalnych indiziach, jeśli kolumny użyte w wywołaniach funkcjonalnych są również zawarte. Czy możesz podać jakiekolwiek odniesienie do dokumentacji wspierającej to; to bardzo sprzeczne z intuicją. To jest ... jak "teraz wiesz". –

+0

Jaka jest pełna definicja tabeli 'xmltable'? Jaki jest plan wykonania instrukcji, jeśli indeks jest obecny? Czy Postgres korzysta z indeksu do wyszukiwania indeksu, czy w ogóle nie korzysta z tego indeksu? –

+0

Wiem, bo próbowałem zi bez xml, zwykłe tabele, json itp. Wydało mi się to również dziwne - będę edytować pytanie, aby dołączyć to – billdoor

Odpowiedz

7

EDIT KOŃCOWA: Dlaczego niemożliwe jest

Zgodnie z dokumentacją: postgresql może zrobić indexonly skanuje gdy typ indeksu wspiera ten (tj btree zawsze wspiera tego GIST i SpGiST jedynie dla konkretnych operatorów, a GIN nie jest w ogóle zdolny). I możliwe jest zrekonstruowanie oryginalnej indeksowanej wartości z indeksu.

Drugi wymóg jest najbardziej interesujący.

W przypadku kolumn jest prosty (a, b), a indeks jest w stanie zrekonstruować oryginalną wartość przechowywaną.

W przypadku funkcji służących do włączania indeksu funkcjonalnego należy utworzyć indeks z oryginalnymi wartościami. Oznacza to, że indeks (f1(a), f2(b)) ponownie pojawi się w tabeli, ponieważ nie można zrekonstruować indeksowanych danych (a, b) z tych wartości. Proponowane przez deweloper obejście polega na utworzeniu indeksu (f1(a), f2(b), a, b) w tym przypadku planista zapytań jest w stanie określić, że możliwe jest uruchomienie skanowania tylko do indeksu, ponieważ indeks zawiera oryginalne dane.

I wracając do pytania, aby utworzyć skanowanie tylko index na kolumnie xml jest niemożliwe: nie ma operatorów obsługujących porównywanie danych XML istotne dla btree. Nie ma definicji operatorów porównania dla danych XML. i dlatego nie można używać tej kolumny w żadnym indeksie, ale jest ona potrzebna w skrypcie indeksu do wskazania optymalizatora zapytań w celu przeprowadzenia skanowania tylko do indeksu.

EDIT: (roztwór jak osiągnąć indeksu skanować tylko na konkretnych wyrażeń XPath)

Jeśli wiesz, że te dane będą często używane, polecam, aby rozwiązać ten problem za pomocą funkcji wyzwalania i tworzenie 2 więcej pól i pokryj je według indeksu.Coś takiego:

ALTER TABLE public.xmltable ADD COLUMN xpath_uuid character varying(36); 
ALTER TABLE public.xmltable ADD COLUMN xpath_title character varying(100); 


CREATE INDEX idx_covering_materialized_xml_data 
    ON public.xmltable 
    USING btree 
    (xpath_uuid COLLATE pg_catalog."default", xpath_title COLLATE pg_catalog."default"); 

CREATE OR REPLACE FUNCTION public.introduce_xml_materialization() 
    RETURNS trigger AS 
$BODY$BEGIN 

NEW.xpath_uuid = (xpath('/document/uuid/text()', NEW.xmldata))[1]::text; 
NEW.xpath_title = (xpath('/document/title/text()', NEW.xmldata))[1]::text; 

RETURN NEW; 
END;$BODY$ 
    LANGUAGE plpgsql STABLE 
    COST 100; 



CREATE TRIGGER index_xml_data 
    BEFORE INSERT OR UPDATE 
    ON public.xmltable 
    FOR EACH ROW 
    EXECUTE PROCEDURE public.introduce_xml_materialization(); 

a następnie można zrobić po prostu:

SELECT xpath_uuid, xpath_title 
    FROM public.xmltable 
    where xpath_uuid = ' uuid1 ' 

który pokaże wskaźnik tylko skanować:

"Index Only Scan using idx_covering_materialized_xml_data on xmltable (cost=0.14..8.16 rows=1 width=308)" 
" Index Cond: (xpath_uuid = ' uuid1 '::text)" 

Takie podejście byłoby optymalne, przy założeniu, że dane czyta się więcej niż pisemnie. Od kosztu wstawienia lub aktualizacji jest na ogół taki sam jak tworzenie funkcjonalnego indeksu na wyrażeniach xpath.

ORIGINAL ODPOWIEDŹ: (może być interesujący dla tych, którzy chcą dostosować optymalizator kwerendy)

Dobrze problem jest to, że optymalizator kwerendy uważa, że ​​wywołanie funkcji xPath jest najprostsze. to jest jak wywoływanie prostego operatora matematycznego, a jego koszt to 1. W tym przypadku optymalizator zapytań myśli, że łatwiej jest pobrać z tabeli i jeszcze raz obliczyć, a następnie czyścić tylko czysty indeks.

JEŚLI zwiększysz koszt wywołania xpath, powiedzmy 1000, optymalizator zapytań zobaczy, że takie wywołanie jest znacznie trudniejsze (to jest faktycznie prawda) i spróbuje wykonać skanowanie tylko do indeksu. W mojej konfiguracji testowej miałem stracony

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

i plan realizacji jest

"Bitmap Heap Scan on xmltable (cost=4.17..11.30 rows=3 width=64)" 
" Recheck Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)" 
" -> Bitmap Index Scan on idx_covering_index_3 (cost=0.00..4.17 rows=3 width=0)" 
"  Index Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)" 

Ale kiedy zrobić

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

plan wykonania jest przejście do indeksu tylko skanować

"Index Scan using idx_covering_index_3 on xmltable (cost=0.15..31.20 rows=3 width=64)" 
" Index Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)" 

I na mojej głośności (np. brak danych) minimalny koszt kwerendy tylko do indeksu jest znacznie mniejszy niż w oryginalnym indeksie + skanowanie tabeli, ale większy koszt jest większy. Tak więc, aby oszukiwać przy zapytaniu, może być wymagane umieszczenie jeszcze wyższych wartości na kosztach połączenia xpath.

Mam nadzieję, że to pomoże, a z ciekawości po prostu pokaż nam korzyści z używania zapytań indeksowych.

+0

planu zapytania w twoim przykładzie nie jest przełączany na skanowanie tylko do indeksu, ale do skanowania indeksu - może nieporozumienie? – billdoor

+0

wystarczy spojrzeć: pierwsze zapytanie (gdy koszt xPath wynosi 1) przechodzi przez indeks (Bitmap Index Scan na idx_covering_index_3), a następnie pobiera dane z tabeli (Bitmap Heap Scan na xmltable). Druga kwerenda (gdy koszt xPath wynosi 100) jest uruchamiana tylko przez indeks (Skanowanie indeksu za pomocą idx_covering_index_3) i nie skanuje tabeli. –

+0

Nie jest to dokładnie to, o co prosiłeś (* Index Only Scan *), ale może to być znaczna poprawa. Więc przegłosuję. –

Powiązane problemy