sql >> Database teknologi >  >> RDS >> PostgreSQL

Noter om PostgreSQL B-Tree-indekser

PostgreSQL kommer med ikke mindre end 6 forskellige typer indekser, hvor B-Treeindex er det mest brugte. Læs videre for at finde ud af mere om B-Treeindexes i PostgreSQL.

Indekstyper

Et indeks i PostgreSQL, som dem, der er oprettet til PRIMÆRE NØGLER og UNIQUE i en CREATE TABLE-sætning eller oprettet eksplicit med en CREATE INDEX-sætning, er af en bestemt "type" (selv om vi teknisk set burde kalde dem "indeksadgangsmetoder").

PostgreSQL kommer med disse indbyggede indekstyper:

  • B-træ
  • Hash
  • GIN – Generaliseret omvendt indeks
  • BRIN – Blokområdeindeks (kun i v9.5 og nyere)
  • GiST – Generaliseret omvendt søgetræ
  • SP-GiST – Space Partitioned GiST

B-Tree er standard og den mest almindeligt anvendte indekstype. Angivelse af en primær nøgle eller en unik i en CREATE TABLE-sætning får PostgreSQL til at oprette B-Tree-indekser. CREATE INDEX-sætninger uden USING-sætningen vil også skabe B-Tree-indekser:

-- 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);

Bestilling

B-Tree indekser er iboende ordnede. PostgreSQL kan gøre brug af denne rækkefølge i stedet for at sortere på det indekserede udtryk. For eksempel ville det kræve en sortering at få titlerne på alle 80'er-film sorteret efter titel:

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)

Men hvis du sorterer dem efter den indekserede kolonne (år), er yderligere sortering ikke påkrævet.

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)

Fyldfaktor

For tabeller, der ikke vil blive opdateret, kan du øge "fyldningsfaktoren" fra standardværdien på 90, hvilket skulle give dig lidt mindre og hurtigere indekser. Omvendt, hvis der er hyppige opdateringer af tabellen, der involverer den indekserede parameter, kan du reducere udfyldningsfaktoren til et mindre antal – dette vil give mulighed for hurtigere indsættelser og opdateringer på bekostning af lidt større indekser.

CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);

Indeksering på tekst

B-Tree indekser kan hjælpe med præfiksmatchning af tekst. Lad os tage en forespørgsel for at liste alle film, der starter med bogstavet '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)

Denne plan kræver en fuld sekventiel scanning af tabellen. Hvad sker der, hvis vi tilføjer et B-Tree-indeks på 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)

Nå, det hjalp overhovedet ikke. Der er dog en form for magisk nissestøv, som vi kan drysse for at få Postgres til at gøre, hvad vi vil:

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)

Planen bruger nu et indeks, og omkostningerne er reduceret. Magien her er "text_pattern_ops", som tillader B-Tree-indekset over et "tekst"-udtryk at blive brugt til mønsteroperatorer (LIKE og regulære udtryk). "text_pattern_ops" kaldes en OperatorClass.

Bemærk, at dette kun vil virke for mønstre med et fast tekstpræfiks, så "%Angry%" eller "%Men" vil ikke fungere. Brug PostgreSQL's fuldtekstsøgning til avancerede tekstforespørgsler.

Dækkende indekser

Dækkende indekser blev tilføjet til PostgreSQL i v11. Ved at dække indekser kan du inkludere værdien af ​​et eller flere udtryk sammen med det indekserede udtryk inde i indekset.

Lad os prøve at søge efter alle filmtitler, sorteret efter udgivelsesår:

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)

Dette involverer en fuld sekventiel scanning af tabellen, efterfulgt af en slags projicerede kolonner. Lad os først tilføje et almindeligt indeks på 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 beslutter Postgres at bruge indekset til at trække posterne ud af tabellen direkte i den ønskede rækkefølge. Tabellen skal slås op, fordi indekset kun indeholder værdien 'år' og referencen til tuple i tabellen.

Hvis vi inkluderer værdien af ​​'titel' også i indekset, kan tabelopslag undgås totalt. Lad os bruge den nye syntaks til at oprette sådan et indeks:

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 bruger nu en Index OnlyScan, hvilket betyder, at tabelopslaget er fuldstændig undgået. Bemærk, at vi var nødt til at droppe det gamle indeks, fordi Postgres ikke valgte ix_year_cov frem for ix_year til denne forespørgsel.

Klyngedannelse

PostgreSQL understøtter berygtet ikke automatisk fysisk bestilling af rækker i en tabel, i modsætning til "clustered indexes" i andre RDBMS. Hvis de fleste af dine forespørgsler skal trække de fleste rækker ud af en for det meste statisk tabel i en fast rækkefølge, ville det være en god idé at layoute det fysiske tabellager i den rækkefølge og bruge sekventielle scanninger. For at omarrangere en tabel fysisk i den rækkefølge, der er dikteret af et indeks, skal du bruge:

CLUSTER VERBOSE movies USING ix_year;

Du vil typisk bruge et B-Tree-indeks til at samle en tabel, da det giver en komplet rækkefølge for alle rækkerne i tabellen.

Indeksstatistik

Hvor meget diskplads optager dit indeks? Funktionen pg_relation_size kan svare på det:

idxdemo=# select * from pg_relation_size('ix_year');
 pg_relation_size
------------------
           663552
(1 row)

Dette returnerer den diskplads, der bruges af indekset, i bytes.

Mere information om indekset kan indsamles ved hjælp af standard extensionpgstattuple. Før du bruger funktionerne nedenfor, skal du lave en CREATE EXTENSION pgstattuple; i den relevante database som superbruger. Brug af disse funktioner kræver også superbrugerrettigheder.

pgstattuple funktion returnerer blandt andet den ubrugte (free_space )og genbrugelig (dead_tuple_len ) diskplads i indekset. Dette kan være meget nyttigt til at beslutte, om en REINDEX skal køres for at reducere indeksbloat.

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 funktion returnerer B-Tree-specifik information, inklusive træets niveau:

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

Dette kan bruges til at beslutte, om indeksets fyldfaktor skal justeres.

Undersøgelse af B-Tree indeksindhold

Selv indholdet af B-Tree kan undersøges direkte ved hjælp af extensionpageinspect. Brugen af ​​denne udvidelse kræver superbrugerrettigheder.

Her er egenskaberne for en enkelt side (her den 13. side) i indekset:

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

Og her er det faktiske indhold af hver vare (begrænset til 5 her) på siden:

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)

Og hvis du tænker på at skrive en forespørgsel for at samle noget over hver side, skal du også bruge det samlede antal sider i relationen, som kan genereres via pg_relpages fra pgstattuple udvidelse:

idxdemo=# select pg_relpages('ix_year');
 pg_relpages
-------------
          81
(1 row)

Andre indekstyper

B-Tree-indekser er alsidige værktøjer til optimering af forespørgsler. Med en smule eksperimentering og planlægning kan den bruges til at forbedre svartiderne for ansøgninger og rapportere job markant.

De andre indekstyper af PostgreSQL er også nyttige og kan være mere effektive og mere effektive end B-Tree i specifikke tilfælde. Denne artikel giver et hurtigt overblik over alle typerne.

Har du et tip om indekser, som du gerne vil dele? Efterlad dem som en kommentar nedenfor!


  1. Sådan sammenlignes to tabeller kolonne for kolonne i orakel

  2. Fundamentals of Table Expressions, Del 13 – Inline Table-Valued Functions, Fortsat

  3. Hvordan ændrer jeg datoformatet i Postgres?

  4. Sådan fungerer SIGN() i MariaDB