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

Note sugli indici B-Tree di PostgreSQL

PostgreSQL viene fornito con non meno di 6 diversi tipi di indici, con il B-Treeindex che è il più comunemente usato. Continua a leggere per saperne di più su B-Treeindexes in PostgreSQL.

Tipi di indici

Un indice in PostgreSQL, come quelli creati per PRIMARY KEY e UNIQUE in un'istruzione CREATE TABLE o creati esplicitamente con un'istruzione CREATE INDEX, sono di un "tipo" particolare (sebbene tecnicamente dovremmo chiamarli "metodi di accesso all'indice").

PostgreSQL viene fornito con questi tipi di indice integrati:

  • B-Albero
  • Hash
  • GIN – Indice invertito generalizzato
  • BRIN – Indice di intervallo di blocchi (solo nella versione 9.5 e successive)
  • GiST – Albero di ricerca invertito generalizzato
  • SP-GiST – GiST a partizioni spaziali

B-Tree è il tipo di indice predefinito e più comunemente utilizzato. La specifica di una chiave primaria o univoca all'interno di un'istruzione CREATE TABLE fa sì che PostgreSQL crei indici B-Tree. Le istruzioni CREATE INDEX senza la clausola USING creeranno anche indici 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);

Ordine

Gli indici B-Tree sono intrinsecamente ordinati. PostgreSQL può utilizzare questo ordine anziché eseguire l'ordinamento sull'espressione indicizzata. Ad esempio, per ordinare i titoli di tutti i film degli anni '80 in base al titolo è necessario un ordinamento:

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)

Ma se li stai ordinando in base alla colonna indicizzata (anno), non è necessario un ordinamento aggiuntivo.

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)

Fattore di riempimento

Per le tabelle che non verranno aggiornate, puoi aumentare il "fattore di riempimento" dal valore predefinito di 90, che dovrebbe darti indici leggermente più piccoli e veloci. Al contrario, se ci sono aggiornamenti frequenti della tabella che coinvolgono il parametro indicizzato, puoi ridurre il fattore di riempimento a un numero inferiore:ciò consentirà inserimenti e aggiornamenti più rapidi, al costo di indici leggermente più grandi.

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

Indicizzazione su testo

Gli indici B-Tree possono aiutare nella corrispondenza dei prefissi del testo. Prendiamo una query per elencare i film che iniziano con la lettera "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)

Questo piano richiede una scansione sequenziale completa della tabella. Cosa succede se aggiungi un indice B-Tree su 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)

Bene, questo non ha aiutato affatto. Tuttavia, esiste una forma di polvere magica che possiamo cospargere per far fare a Postgres ciò che vogliamo:

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)

Il piano ora utilizza un indice e il costo è stato ridotto. La magia qui è "text_pattern_ops" che consente all'indice B-Tree su un'espressione "testo" di essere utilizzato per operatori di pattern (LIKE ed espressioni regolari). Il "text_pattern_ops" è chiamato OperatorClass.

Nota che funzionerà solo per i modelli con un prefisso di testo fisso, quindi "%Angry%" o "%Men" non funzioneranno. Usa la ricerca full-text di PostgreSQL per le query di testo avanzate.

Indici di copertura

Gli indici di copertura sono stati aggiunti a PostgreSQL nella v11. Gli indici di copertura ti consentono di includere il valore di una o più espressioni insieme all'espressione indicizzata all'interno dell'indice.

Proviamo a cercare tutti i titoli dei film, ordinati per anno di uscita:

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)

Ciò comporta una scansione sequenziale completa della tabella, seguita da una sorta di colonne proiettate. Aggiungiamo prima un indice regolare su 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)

Ora Postgres decide di utilizzare l'indice per estrarre le voci dalla tabella direttamente nell'ordine desiderato. La tabella deve essere cercata perché l'indice contiene solo il valore di 'anno' e il riferimento alla tupla nella tabella.

Se includiamo il valore di 'titolo' anche all'interno dell'indice, la ricerca nella tabella può essere evitata del tutto. Usiamo la nuova sintassi per creare un tale indice:

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 ora utilizza un Index OnlyScan, il che significa che la ricerca nella tabella è completamente evitata. Nota che abbiamo dovuto eliminare il vecchio indice, perché Postgres non ha scelto ix_year_cov su ix_year per questa query.

Raggruppamento

PostgreSQL notoriamente non supporta l'ordinamento fisico automatico delle righe in una tabella, a differenza degli "indici cluster" in altri RDBMS. Se la maggior parte delle tue query estrarrà la maggior parte delle righe di una tabella per lo più statica in un ordine fisso, sarebbe una buona idea disporre la memoria fisica della tabella in quell'ordine e utilizzare scansioni sequenziali. Per riordinare fisicamente una tabella nell'ordine dettato da un indice, utilizzare:

CLUSTER VERBOSE movies USING ix_year;

In genere utilizzeresti un indice B-Tree per riclassificare una tabella, poiché fornisce un ordine completo per tutte le righe della tabella.

Statistiche dell'indice

Quanto spazio su disco occupa il tuo indice? La funzione pg_relation_size può rispondere a:

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

Questo restituisce lo spazio su disco utilizzato dall'indice, in byte.

Ulteriori informazioni sull'indice possono essere raccolte utilizzando lo standard extensionpgstattuple. Prima di utilizzare le funzioni seguenti, devi eseguire un CREATE EXTENSION pgstattuple; nel database pertinente come superutente. L'utilizzo di queste funzioni richiede anche i privilegi di superutente.

Il pgstattuple La funzione restituisce, tra le altre cose, l'inutilizzato (free_space ) e riutilizzabile (dead_tuple_len ) spazio su disco all'interno dell'indice. Questo può essere molto utile per decidere se eseguire un REINDEX per ridurre il rigonfiamento dell'indice.

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

Il pgstattuple La funzione restituisce informazioni specifiche sull'albero B, incluso il livello dell'albero:

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

Questo può essere utilizzato per decidere se regolare il fattore di riempimento dell'indice.

Esame del contenuto dell'indice B-Tree

Anche i contenuti del B-Tree possono essere esaminati direttamente, utilizzando extensionpageinspect. L'utilizzo di questa estensione richiede privilegi di superutente.

Ecco le proprietà di una singola pagina (qui, la 13a pagina) dell'indice:

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

Ed ecco i contenuti effettivi di ogni elemento (limitato a 5 qui) nella 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)

E se stai pensando di scrivere una query per aggregare qualcosa su ogni pagina, avrai anche bisogno del numero totale di pagine nella relazione, che può essere generato tramite pg_relpages dalla pgstattuple estensione:

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

Altri tipi di indici

Gli indici B-Tree sono strumenti versatili per ottimizzare le query. Con un po' di sperimentazione e pianificazione, può essere utilizzato per migliorare notevolmente i tempi di risposta delle applicazioni e segnalare i lavori.

Anche gli altri tipi di indice di PostgreSQL sono utili e possono essere più efficienti e performanti di B-Tree in casi specifici. Questo articolo offre una rapida panoramica di tutti i tipi.

Hai un suggerimento sugli indici che vorresti condividere? Lasciali come commento qui sotto!