PostgreSQL přichází s ne méně než 6 různými typy indexů, přičemž B-Treeindex je nejčastěji používaný. Čtěte dále a zjistěte více o B-Treeindexech v PostgreSQL.
Typy indexů
Indexy v PostgreSQL, jako ty vytvořené pro PRIMARY KEYs a UNIQUE v příkazu aCREATE TABLE nebo vytvořené explicitně pomocí příkazu CREATE INDEX, jsou určitého „typu“ (ačkoli bychom je technicky měli nazývat „metody přístupu k indexu“).
PostgreSQL přichází s těmito vestavěnými typy indexů:
- B-strom
- Hash
- GIN – Generalized Inverted Index
- BRIN – Index rozsahu bloků (pouze ve verzi 9.5 a vyšší)
- GiST – Generalized Inverted Search Tree
- SP-GiST – Space Partitioned GiST
B-Strom je výchozí a nejčastěji používaný typ indexu. Zadání primárního klíče nebo jedinečného klíče v příkazu CREATE TABLE způsobí, že PostgreSQL vytvoří indexy B-stromu. Příkazy CREATE INDEX bez klauzule USING vytvoří také indexy B-Stromu:
-- 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);
Objednávání
Indexy B-Stromu jsou ze své podstaty řazeny. PostgreSQL může využít toto pořadí spíše než řazení podle indexovaného výrazu. Například řazení názvů všech filmů z 80. let podle názvu by vyžadovalo řazení:
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)
Pokud je ale třídíte podle indexovaného sloupce (rok), další řazení není vyžadováno.
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)
Faktor plnění
U tabulek, které nebudou aktualizovány, můžete zvýšit „faktor plnění“ z výchozích 90, což by vám mělo poskytnout o něco menší a rychlejší indexy. Naopak, pokud dochází k častým aktualizacím tabulky zahrnující indexovaný parametr, můžete faktor plnění snížit na menší číslo – to umožní rychlejší vkládání a aktualizace za cenu o něco větších indexů.
CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);
Indexování textu
Indexy B-Stromu mohou pomoci při shodě s prefixem textu. Vezměme si dotaz na seznam všech filmů začínajících písmenem „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)
Tento plán vyžaduje úplné sekvenční skenování tabulky. Co se stane, když přidáte index B-Strom na 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)
No, to vůbec nepomohlo. Existuje však forma magického skřítkového prachu, kterou můžeme posypat, aby Postgres dělal, co chceme:
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)
Plán nyní používá index a náklady se snížily. Kouzlo je zde „text_pattern_ops“, které umožňuje použití indexu B-Stromu nad „textovým“ výrazem pro operátory vzorů (LIKE a regulární výrazy). „text_pattern_ops“ se nazývá OperatorClass.
Pamatujte, že to bude fungovat pouze pro vzory s pevnou textovou předponou, takže „%Angry%“ nebo „%Men“ nebude fungovat. Použijte fulltextové vyhledávání PostgreSQL pro pokročilé textové dotazy.
Pokrývající indexy
Krycí indexy byly přidány do PostgreSQL ve verzi 11. Krycí indexy vám umožňují zahrnout hodnotu jednoho nebo více výrazů spolu s indexovaným výrazem do indexu.
Zkusme se zeptat na všechny názvy filmů, seřazené podle roku vydání:
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)
To zahrnuje úplné sekvenční skenování tabulky, po kterém následují jakési projektované sloupce. Nejprve přidáme pravidelný index na 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)
Nyní se Postgres rozhodne použít index k vytažení záznamů z tabulky přímo v požadovaném pořadí. Tabulku je třeba vyhledat, protože index obsahuje pouze hodnotu ‚rok‘ a odkaz na n-tici v tabulce.
Pokud do indexu zahrneme i hodnotu „title“, lze se zcela vyhnout vyhledávání v tabulce. K vytvoření takového indexu použijeme novou syntaxi:
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 nyní používá Index OnlyScan, což znamená, že je zcela vyloučeno vyhledávání v tabulce. Všimněte si, že jsme museli vypustit starý index, protože Postgres pro tento dotaz nezvolil ix_year_cov nad ix_year.
Shlukování
PostgreSQL neslavně nepodporuje automatické fyzické řazení řádků v tabulce, na rozdíl od „shlukovaných indexů“ v jiných RDBMS. Pokud většina vašich dotazů vytáhne většinu řádků převážně statické tabulky v pevném pořadí, bylo by dobré rozmístit úložiště fyzické tabulky v tomto pořadí a použít sekvenční skenování. Chcete-li změnit pořadí tabulky fyzicky v pořadí diktovaném indexem, použijte:
CLUSTER VERBOSE movies USING ix_year;
K opětovnému seskupení tabulky byste obvykle použili index B-Tree, protože poskytuje úplné pořadí pro všechny řádky v tabulce.
Statistiky indexu
Kolik místa na disku zabírá váš index? Funkce pg_relation_size může odpovědět:
idxdemo=# select * from pg_relation_size('ix_year');
pg_relation_size
------------------
663552
(1 row)
Tím se vrátí místo na disku používané indexem v bajtech.
Další informace o indexu lze získat pomocí standardního rozšířenípgstattuple. Než použijete níže uvedené funkce, musíte provést CREATE EXTENSION pgstattuple;
v příslušné databázi jako superuživatel. Použití těchto funkcí také vyžaduje oprávnění superuživatele.
pgstattuple
funkce vrací mimo jiné nepoužitý (free_space
)a opakovaně použitelné (dead_tuple_len
) místo na disku v rámci indexu. To může být velmi užitečné při rozhodování, zda spustit REINDEX
snížit nadýmání indexu.
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
funkce vrací informace specifické pro B-Strom, včetně úrovně stromu:
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
To lze použít k rozhodnutí, zda upravit faktor plnění indexu.
Zkoumání obsahu indexu B-stromu
Dokonce i obsah B-Stromu lze prozkoumat přímo pomocí rozšíření pageinspect. Použití tohoto rozšíření vyžaduje oprávnění superuživatele.
Zde jsou vlastnosti jedné stránky (zde 13. stránky) indexu:
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 zde je skutečný obsah každé položky (zde omezen na 5) na stránce:
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 pokud uvažujete o napsání dotazu, který by na každé stránce něco agregoval, budete také potřebovat celkový počet stránek ve vztahu, který lze získat pomocí pg_relpages
z pgstattuple
rozšíření:
idxdemo=# select pg_relpages('ix_year');
pg_relpages
-------------
81
(1 row)
Další typy indexů
Indexy B-Tree jsou všestranné nástroje pro optimalizaci dotazů. S trochou experimentování a plánování jej lze použít k výraznému zlepšení doby odezvy aplikací a úloh sestav.
Ostatní typy indexů PostgreSQL jsou také užitečné a ve specifických případech mohou být efektivnější a výkonnější než B-Strom. Tento článek poskytuje rychlý přehled všech typů.
Máte tip na indexy, které byste chtěli sdílet? Zanechte je jako komentář níže!