PostgreSQL zawiera co najmniej 6 różnych typów indeksów, z których B-Treeindex jest najczęściej używany. Czytaj dalej, aby dowiedzieć się więcej o B-Treeindexes w PostgreSQL.
Typy indeksów
Indeksy w PostgreSQL, takie jak te utworzone dla PRIMARY KEY i UNIQUE w instrukcji aCREATE TABLE lub utworzone jawnie za pomocą instrukcji CREATE INDEX, są określonego „typu” (chociaż technicznie powinniśmy nazywać je „metodami dostępu do indeksu”).
PostgreSQL zawiera następujące wbudowane typy indeksów:
- B-drzewo
- Hasz
- GIN – uogólniony indeks odwrócony
- BRIN – Indeks zakresu bloków (tylko w wersji 9.5 i nowszych)
- GiST – uogólnione drzewo wyszukiwania odwróconego
- SP-GiST – GiST z partycjami przestrzeni
B-Tree jest domyślnym i najczęściej używanym typem indeksu. Określenie klucza podstawowego lub unikatowego w instrukcji CREATE TABLE powoduje, że PostgreSQL tworzy indeksy B-Tree. Instrukcje CREATE INDEX bez klauzuli USING będą również tworzyć indeksy B-Tree:
-- the default index type is btree
CREATE INDEX ix_year ON movies (year);
-- equivalent, explicitly lists the index type
CREATE INDEX ix_year ON movies USING btree (year);
Zamawianie
Indeksy B-Tree są z natury uporządkowane. PostgreSQL może wykorzystać tę kolejność zamiast sortowania według zindeksowanego wyrażenia. Na przykład posortowanie tytułów wszystkich filmów z lat 80. według tytułu wymagałoby sortowania:
idxdemo=# explain select title from movies where year between 1980 and 1989 order by title asc;
QUERY PLAN
----------------------------------------------------------------------------------
Sort (cost=240.79..245.93 rows=2056 width=17)
Sort Key: title
-> Index Scan using ix_year on movies (cost=0.29..127.65 rows=2056 width=17)
Index Cond: ((year >= 1980) AND (year <= 1989))
(4 rows)
Ale jeśli sortujesz je według indeksowanej kolumny (roku), dodatkowe sortowanie nie jest wymagane.
idxdemo=# explain select title from movies where year between 1980 and 1989 order by year asc;
QUERY PLAN
----------------------------------------------------------------------------
Index Scan using ix_year on movies (cost=0.29..127.65 rows=2056 width=21)
Index Cond: ((year >= 1980) AND (year <= 1989))
(2 rows)
Współczynnik wypełnienia
W przypadku tabel, które nie będą aktualizowane, możesz zwiększyć „współczynnik wypełnienia” z domyślnego 90, co powinno dać ci nieco mniejsze i szybsze indeksy. I odwrotnie, jeśli zdarzają się częste aktualizacje tabeli zawierającej indeksowany parametr, możesz zmniejszyć współczynnik wypełnienia do mniejszej liczby – pozwoli to na szybsze wstawianie i aktualizowanie, kosztem nieco większych indeksów.
CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);
Indeksowanie tekstu
Indeksy B-Tree mogą pomóc w dopasowaniu przedrostków tekstu. Zróbmy zapytanie, aby wyświetlić wszystkie filmy zaczynające się na literę „T”:
idxdemo=> explain select title from movies where title like 'T%';
QUERY PLAN
-------------------------------------------------------------
Seq Scan on movies (cost=0.00..1106.94 rows=8405 width=17)
Filter: (title ~~ 'T%'::text)
(2 rows)
Ten plan wymaga pełnego sekwencyjnego skanowania tabeli. Co się stanie, jeśli dodamy indeks B-Tree do movies.title?
idxdemo=> create index ix_title on movies (title);
CREATE INDEX
idxdemo=> explain select title from movies where title like 'T%';
QUERY PLAN
-------------------------------------------------------------
Seq Scan on movies (cost=0.00..1106.94 rows=8405 width=17)
Filter: (title ~~ 'T%'::text)
(2 rows)
Cóż, to wcale nie pomogło. Istnieje jednak forma magicznego pyłu pixie, który możemy posypać, aby Postgres zrobił to, czego chcemy:
idxdemo=> create index ix_title2 on movies (title text_pattern_ops);
CREATE INDEX
idxdemo=> explain select title from movies where title like 'T%';
QUERY PLAN
-----------------------------------------------------------------------------
Bitmap Heap Scan on movies (cost=236.08..1085.19 rows=8405 width=17)
Filter: (title ~~ 'T%'::text)
-> Bitmap Index Scan on ix_title2 (cost=0.00..233.98 rows=8169 width=0)
Index Cond: ((title ~>=~ 'T'::text) AND (title ~<~ 'U'::text))
(4 rows)
Plan używa teraz indeksu, a koszt został zmniejszony. Magią jest tutaj „text_pattern_ops”, która pozwala na użycie indeksu B-Tree nad wyrażeniem „tekstowym” dla operatorów wzorców (LIKE i wyrażeń regularnych). „text_pattern_ops” nazywa się OperatorClass.
Pamiętaj, że będzie to działać tylko w przypadku wzorców ze stałym prefiksem tekstowym, więc „%Angry%” lub „%Men” nie będą działać. Skorzystaj z wyszukiwania pełnotekstowego PostgreSQL dla zaawansowanych zapytań tekstowych.
Indeksy pokrycia
Indeksy pokrywające zostały dodane do PostgreSQL w v11. Indeksy pokrywające umożliwiają uwzględnienie wartości jednego lub więcej wyrażeń wraz z indeksowanym wyrażeniem wewnątrz indeksu.
Spróbujmy wyszukać wszystkie tytuły filmów uporządkowane według roku wydania:
idxdemo=# explain select title from movies order by year asc;
QUERY PLAN
--------------------------------------------------------------------
Sort (cost=3167.73..3239.72 rows=28795 width=21)
Sort Key: year
-> Seq Scan on movies (cost=0.00..1034.95 rows=28795 width=21)
(3 rows)
Obejmuje to pełne sekwencyjne skanowanie tabeli, po którym następuje rodzaj rzutowanych kolumn. Najpierw dodajmy zwykły indeks dotyczący movies.year:
idxdemo=# create index ix_year on movies (year);
CREATE INDEX
idxdemo=# explain select title from movies order by year asc;
QUERY PLAN
------------------------------------------------------------------------------
Index Scan using ix_year on movies (cost=0.29..1510.22 rows=28795 width=21)
(1 row)
Teraz Postgres postanawia użyć indeksu do wyciągania wpisów z tabeli bezpośrednio w żądanej kolejności. Należy sprawdzić tabelę, ponieważ indeks zawiera tylko wartość „rok” i odniesienie do krotki w tabeli.
Jeśli uwzględnimy wartość „title” również wewnątrz indeksu, można całkowicie uniknąć wyszukiwania tabeli. Użyjmy nowej składni do stworzenia takiego indeksu:
idxdemo=# create index ix_year_cov on movies (year) include (title);
CREATE INDEX
Time: 92.618 ms
idxdemo=# drop index ix_year;
DROP INDEX
idxdemo=# explain select title from movies order by year asc;
QUERY PLAN
---------------------------------------------------------------------------------------
Index Only Scan using ix_year_cov on movies (cost=0.29..2751.59 rows=28795 width=21)
(1 row)
Postgres używa teraz funkcji Index OnlyScan, co oznacza, że całkowicie unika się przeglądania tabel. Pamiętaj, że musieliśmy usunąć stary indeks, ponieważ Postgres nie wybrał dla tego zapytania ix_year_cov zamiast ix_year.
Klastrowanie
PostgreSQL niesławnie nie obsługuje automatycznego fizycznego porządkowania wierszy w tabeli, w przeciwieństwie do „indeksów klastrowych” w innych RDBMS. Jeśli większość zapytań ma pobierać większość wierszy w większości statycznej tabeli w ustalonej kolejności, dobrym pomysłem byłoby ułożenie fizycznej pamięci tabeli w tej kolejności i użycie sekwencyjnego skanowania. Aby fizycznie zmienić kolejność tabeli w kolejności określonej przez indeks, użyj:
CLUSTER VERBOSE movies USING ix_year;
Zazwyczaj do ponownego klastrowania tabeli używa się indeksu B-Tree, ponieważ zapewnia on kompletną kolejność wszystkich wierszy w tabeli.
Statystyki indeksu
Ile miejsca na dysku zajmuje Twój indeks? Funkcja pg_relation_size może odpowiedzieć, że:
idxdemo=# select * from pg_relation_size('ix_year');
pg_relation_size
------------------
663552
(1 row)
Zwraca miejsce na dysku używane przez indeks w bajtach.
Więcej informacji o indeksie można uzyskać za pomocą standardowego rozszerzenia pgstattuple. Zanim użyjesz poniższych funkcji, musisz wykonać CREATE EXTENSION pgstattuple;
w odpowiedniej bazie danych jako superużytkownik. Korzystanie z tych funkcji wymaga również uprawnień superużytkownika.
pgstattuple
funkcja zwraca m.in. nieużywane (free_space
) i wielokrotnego użytku (dead_tuple_len
) miejsce na dysku w indeksie. Może to być bardzo pomocne przy podejmowaniu decyzji, czy uruchomić REINDEX
aby zmniejszyć rozdęcie indeksu.
idxdemo=# select * from pgstattuple('ix_year'::regclass);
-[ RECORD 1 ]------+-------
table_len | 663552
tuple_count | 28795
tuple_len | 460720
tuple_percent | 69.43
dead_tuple_count | 0
dead_tuple_len | 0
dead_tuple_percent | 0
free_space | 66232
free_percent | 9.98
pgstattuple
funkcja zwraca informacje specyficzne dla B-drzewa, w tym poziom drzewa:
idxdemo=# select * from pgstatindex('ix_year'::regclass);
-[ RECORD 1 ]------+-------
version | 2
tree_level | 1
index_size | 663552
root_block_no | 3
internal_pages | 1
leaf_pages | 79
empty_pages | 0
deleted_pages | 0
avg_leaf_density | 89.72
leaf_fragmentation | 0
Można to wykorzystać do podjęcia decyzji, czy dostosować współczynnik wypełnienia indeksu.
Badanie zawartości indeksu B-Tree
Nawet zawartość B-Tree można sprawdzić bezpośrednio, używając extensionpageinspect. Korzystanie z tego rozszerzenia wymaga uprawnień administratora.
Oto właściwości pojedynczej strony (tutaj, 13. strona) indeksu:
idxdemo=# select * from bt_page_stats('ix_year', 13);
-[ RECORD 1 ]-+-----
blkno | 13
type | l
live_items | 367
dead_items | 0
avg_item_size | 16
page_size | 8192
free_size | 808
btpo_prev | 12
btpo_next | 14
btpo | 0
btpo_flags | 1
A oto rzeczywista zawartość każdego elementu (ograniczona do 5 tutaj) na stronie:
idxdemo=# select * from bt_page_items('ix_year', 13) limit 5;
itemoffset | ctid | itemlen | nulls | vars | data
------------+----------+---------+-------+------+-------------------------
1 | (104,40) | 16 | f | f | 86 07 00 00 00 00 00 00
2 | (95,38) | 16 | f | f | 86 07 00 00 00 00 00 00
3 | (95,39) | 16 | f | f | 86 07 00 00 00 00 00 00
4 | (95,40) | 16 | f | f | 86 07 00 00 00 00 00 00
5 | (96,1) | 16 | f | f | 86 07 00 00 00 00 00 00
(5 rows)
A jeśli myślisz o napisaniu zapytania, aby zagregować coś na każdej stronie, będziesz potrzebować również całkowitej liczby stron w relacji, którą można utworzyć za pomocą pg_relpages
z pgstattuple
rozszerzenie:
idxdemo=# select pg_relpages('ix_year');
pg_relpages
-------------
81
(1 row)
Inne typy indeksów
Indeksy B-Tree to wszechstronne narzędzia do optymalizacji zapytań. Przy odrobinie eksperymentów i planowania można go wykorzystać do znacznej poprawy czasów odpowiedzi aplikacji i zadań raportowania.
Inne typy indeksów PostgreSQL są również przydatne i mogą być bardziej wydajne i wydajne niż B-Tree w określonych przypadkach. Ten artykuł zawiera szybki przegląd wszystkich typów.
Masz wskazówkę dotyczącą indeksów, które chcesz udostępnić? Zostaw je jako komentarz poniżej!