PostgreSQL wordt geleverd met maar liefst 6 verschillende soorten indexen, waarvan de B-Treeindex de meest gebruikte is. Lees verder om meer te weten te komen over B-Treeindexes in PostgreSQL.
Soorten indexen
Een index in PostgreSQL, zoals die gemaakt voor PRIMARY KEYs en UNIQUEs in aCREATE TABLE-instructie of expliciet gemaakt met een CREATE INDEX-instructie, is van een bepaald "type" (hoewel we ze technisch gezien "indexaccess-methoden" zouden moeten noemen).
PostgreSQL wordt geleverd met deze ingebouwde indextypen:
- B-Tree
- Hash
- GIN – gegeneraliseerde geïnverteerde index
- BRIN – Block Range Index (alleen in v9.5 en hoger)
- GiST – Gegeneraliseerde omgekeerde zoekboom
- SP-GiST – Ruimtegepartitioneerde GiST
B-Tree is het standaard en het meest gebruikte indextype. Het specificeren van een primaire sleutel of een unieke sleutel binnen een CREATE TABLE-instructie zorgt ervoor dat PostgreSQL B-Tree-indexen maakt. CREATE INDEX-instructies zonder de clausule USING zullen ook B-Tree-indexen maken:
-- 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);
Bestellen
B-Tree-indexen zijn inherent geordend. PostgreSQL kan deze volgorde gebruiken in plaats van te sorteren op de geïndexeerde expressie. Als u bijvoorbeeld de titels van alle films uit de jaren 80 op titel wilt sorteren, moet u het volgende sorteren:
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)
Maar als u ze sorteert op de geïndexeerde kolom (jaar), is extra sorteren niet vereist.
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)
Vulfactor
Voor tabellen die niet worden bijgewerkt, kunt u de "vulfactor" verhogen van de standaardwaarde van 90, wat u iets kleinere en snellere indexen zou moeten geven. Omgekeerd, als de tabel regelmatig wordt bijgewerkt met de geïndexeerde parameter, kunt u de vulfactor verlagen tot een kleiner aantal - dit zorgt voor snellere invoegingen en updates, ten koste van iets grotere indexen.
CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);
Indexeren op tekst
B-Tree-indexen kunnen helpen bij het matchen van voorvoegsels van tekst. Laten we een zoekopdracht uitvoeren om een lijst te maken van alle films die beginnen met de letter '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)
Dit plan vereist een volledige sequentiële scan van de tafel. Wat gebeurt er als we een B-Tree-index toevoegen aan 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)
Nou, dat hielp helemaal niets. Er is echter een vorm van magisch elfenstof die we kunnen strooien om Postgres te laten doen wat we willen:
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)
Het plan gebruikt nu een index en de kosten zijn verlaagd. De magie hier is "text_pattern_ops", waarmee de B-Tree-index over een "tekst" -expressie kan worden gebruikt voor patroonoperators (LIKE en reguliere expressies). De "text_pattern_ops" wordt een OperatorClass genoemd.
Merk op dat dit alleen werkt voor patronen met een vast tekstvoorvoegsel, dus "%Angry%" of "%Men" zal niet werken. Gebruik de volledige tekstzoekfunctie van PostgreSQL voor geavanceerde tekstquery's.
Dekkende indexen
Dekkende indexen zijn toegevoegd aan PostgreSQL in v11. Met dekkende indexen kunt u de waarde van een of meer uitdrukkingen samen met de geïndexeerde uitdrukking in de index opnemen.
Laten we eens zoeken naar alle filmtitels, gerangschikt op jaar van uitgave:
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)
Dit omvat een volledige sequentiële scan van de tabel, gevolgd door een soort van de geprojecteerde kolommen. Laten we eerst een reguliere index toevoegen aan 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)
Nu besluit Postgres de index te gebruiken om de items direct in de gewenste volgorde uit de tabel te halen. De tabel moet worden opgezocht omdat de index alleen de waarde van 'jaar' en de verwijzing naar de tuple in de tabel bevat.
Als we de waarde van 'title' ook in de index opnemen, kan het opzoeken van tabellen volledig worden vermeden. Laten we de nieuwe syntaxis gebruiken om zo'n index te maken:
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 gebruikt nu een Index OnlyScan, wat betekent dat het opzoeken van tabellen volledig wordt vermeden. Merk op dat we de oude index moesten laten vallen, omdat Postgres voor deze zoekopdracht niet ix_year_cov over ix_year heeft gekozen.
Clustering
PostgreSQL ondersteunt berucht geen automatische fysieke volgorde van rijen in een tabel, in tegenstelling tot "geclusterde indexen" in andere RDBMS. Als de meeste van uw zoekopdrachten de meeste rijen van een overwegend statische tabel in een vaste volgorde eruit halen, zou het een goed idee zijn om de fysieke tabelopslag in die volgorde in te delen en opeenvolgende scans te gebruiken. Om een tabel fysiek opnieuw te ordenen in de volgorde die wordt bepaald door een index, gebruikt u:
CLUSTER VERBOSE movies USING ix_year;
Normaal gesproken gebruikt u een B-Tree-index om een tabel opnieuw te clusteren, omdat deze een volledige volgorde biedt voor alle rijen in de tabel.
Indexstatistieken
Hoeveel schijfruimte neemt uw index in beslag? De pg_relation_sizefunctie kan dat beantwoorden:
idxdemo=# select * from pg_relation_size('ix_year');
pg_relation_size
------------------
663552
(1 row)
Dit geeft de schijfruimte terug die door de index wordt gebruikt, in bytes.
Meer informatie over de index kan worden verzameld met behulp van de standaardextensie pgstattuple. Voordat u de onderstaande functies gebruikt, moet u een CREATE EXTENSION pgstattuple;
doen in de relevante database als superuser. Voor het gebruik van deze functies zijn ook superuser-privileges nodig.
De pgstattuple
functie retourneert onder andere de ongebruikte (free_space
) en herbruikbaar (dead_tuple_len
) schijfruimte binnen de index. Dit kan erg handig zijn om te beslissen of u een REINDEX
. wilt uitvoeren om indexbloat te verminderen.
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
De pgstattuple
functie retourneert B-Tree-specifieke informatie, inclusief het niveau van de boom:
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
Dit kan worden gebruikt om te beslissen of de vulfactor van de index moet worden aangepast.
B-Tree Index-inhoud onderzoeken
Zelfs de inhoud van de B-Tree kan direct worden bekeken met behulp van de extensiepaginainspect. Het gebruik van deze extensie vereist superuser-privileges.
Hier zijn de eigenschappen van een enkele pagina (hier de 13e pagina) van de index:
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
En hier is de feitelijke inhoud van elk item (hier beperkt tot 5) op de pagina:
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)
En als je overweegt een zoekopdracht te schrijven om iets over elke pagina te verzamelen, heb je ook het totale aantal pagina's in de relatie nodig, dat kan worden verkregen via pg_relpages
van de pgstattuple
extensie:
idxdemo=# select pg_relpages('ix_year');
pg_relpages
-------------
81
(1 row)
Andere indextypen
B-Tree-indexen zijn veelzijdige hulpmiddelen voor het optimaliseren van zoekopdrachten. Met een beetje experimenteren en plannen kan het worden gebruikt om de reactietijden van sollicitaties en het rapporteren van vacatures enorm te verbeteren.
De andere indextypen van PostgreSQL zijn ook nuttig en kunnen in specifieke gevallen efficiënter en beter presteren dan B-Tree. Dit artikel geeft een snel overzicht van alle soorten.
Heb je een tip over indexen die je wilt delen? Laat ze hieronder als reactie achter!