it-swarm.it

Funzionamento degli indici in PostgreSQL

Ho un paio di domande sul funzionamento degli indici in PostgreSQL. Ho una tabella Friends con il seguente indice:

   Friends ( user_id1 ,user_id2) 

user_id1 e user_id2 sono chiavi esterne della tabella user

  1. Sono equivalenti? Se no allora perché?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
    
  2. Se creo la chiave primaria (user_id1, user_id2), crea automaticamente gli indici per essa e

    Se gli indici nella prima domanda non sono equivalenti, quale indice viene creato sul comando chiave primaria sopra?

80
codecool

Ecco i risultati dell'interrogazione di una tabella sul seconda colonna di un indice a più colonne.
Gli effetti sono facili da riprodurre per chiunque. Provalo a casa.

Ho provato con PostgreSQL 9.0.5 su Debian usando una tabella di medie dimensioni di un database reale con 23322 righe. Implementa la relazione n: m tra le tabelle adr (indirizzo) e att (attributo), ma non è rilevante qui. Schema semplificato:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

Il vincolo UNIQUE implementa efficacemente un indice univoco. Ho ripetuto il test con un semplice indice per essere sicuro e ho ottenuto risultati identici come previsto.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

La tabella è raggruppata in adratt_uni index e prima del test ho eseguito:

CLUSTER adratt;
ANALYZE adratt;

Scansioni sequenziali per query su (adr_id, att_id) sono più veloci che possono essere. L'indice a più colonne verrà comunque utilizzato per una condizione di query solo sulla seconda colonna dell'indice.

Ho eseguito le query un paio di volte per popolare la cache e ho scelto la migliore delle dieci esecuzioni per ottenere risultati comparabili.

1. Interrogazione utilizzando entrambe le colonne

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

Output di EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2. Interrogazione utilizzando la prima colonna

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

Output di EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. Interrogazione utilizzando la seconda colonna

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

Output di EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4. Disabilita indexscan e bitmapscan

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

Uscita di EXPLAIN ANALYZE:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

Output di EXPLAIN ANALYZE:

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

Conclusione

Come previsto, l'indice a più colonne viene utilizzato per una query sulla sola seconda colonna.
Come previsto, è meno efficace, ma la query è ancora volte più veloce che senza l'indice.
Dopo aver disabilitato le scansioni dell'indice, il pianificatore di query sceglie una scansione heap bitmap, che si comporta quasi altrettanto velocemente. Solo dopo aver disabilitato anche quello, torna a una scansione sequenziale.

83

re 1) Sì e no.

Per una query che utilizza entrambe le colonne, ad es. where (user_id1, user_id2) = (1,2) non importa quale indice viene creato.

Per una query che ha una condizione su una sola colonna, ad es. where user_id1 = 1 È importante perché di solito solo le colonne "iniziali" possono essere utilizzate per un confronto dall'ottimizzatore. Quindi where user_id1 = 1 Sarebbe in grado di utilizzare l'indice (user_id1, user_id2) ma non sarebbe in grado di utilizzare un indice (user_id2, user_id1) per tutti i casi.

Dopo aver giocato con questo (dopo che Erwin ci ha mostrato così gentilmente una configurazione in cui funziona), sembra che questo dipenda fortemente dalla distribuzione dei dati della seconda colonna, anche se non ho ancora scoperto quale situazione consente all'ottimizzatore di utilizzare le colonne finali per una condizione WHERE.

Oracle 11 che può anche (a volte) utilizzare colonne che non sono all'inizio della definizione dell'indice.

re 2) Sì, creerà un indice

Citazione dal manuale

L'aggiunta di una chiave primaria creerà automaticamente un indice btree univoco sulla colonna o sul gruppo di colonne utilizzate nella chiave primaria.

re 2a) Primary Key (user_id1,user_id2) creerà un indice su (user_id1, user_id2) (che puoi scoprire da solo molto facilmente semplicemente creando una chiave così primaria)

Consiglio vivamente di leggere capitolo sugli indici nel manuale , sostanzialmente risponde a tutte le domande sopra.

Inoltre, Quale indice creare? di depesz fa un buon lavoro spiegando l'ordine su colonne di indice e altri argomenti relativi all'indice.

30

Annuncio 1)
Vi sono delle limitazioni in PostgreSQL come descritto da @a_horse_with_no_name . Fino a versione 8. gli indici a più colonne potevano essere utilizzati solo per le query sulle colonne principali. Questo è stato migliorato nella versione 8.1. Il manuale attuale di Postgres 1 (aggiornato) spiega:

Un indice B-tree a più colonne può essere utilizzato con condizioni di query che coinvolgono qualsiasi sottoinsieme delle colonne dell'indice, ma l'indice è più efficiente in presenza di vincoli sulle colonne principali (più a sinistra). La regola esatta è che i vincoli di uguaglianza sulle colonne iniziali, più eventuali vincoli di disuguaglianza sulla prima colonna che non hanno un vincolo di uguaglianza, saranno utilizzati per limitare la parte dell'indice che viene scansionata. I vincoli sulle colonne a destra di queste colonne sono controllati nell'indice, quindi salvano le visite nella tabella corretta, ma non riducono la parte dell'indice che deve essere scansionata. Ad esempio, dato un indice su (a, b, c) e una condizione della query WHERE a = 5 AND b >= 42 AND c < 77, l'indice dovrebbe essere scansionato dalla prima voce con a = 5 e b = 42 fino all'ultima voce con a = 5. Voci di indice con c> = 77 verrebbero ignorati, ma dovrebbero comunque essere sottoposti a scansione. In linea di principio questo indice potrebbe essere utilizzato per le query che hanno vincoli su b e/o c senza alcun vincolo su a - ma l'intero indice dovrebbe essere scansionato, quindi nella maggior parte dei casi il pianificatore preferirebbe una scansione sequenziale della tabella rispetto all'indice.

Enfasi mia. Posso confermarlo per esperienza.
Vedi anche il caso di prova aggiunto la mia risposta successiva qui .

12

Questo è in risposta a risposta di Jack , un commento non lo farebbe.

Non c'erano nessun indice di copertura in PostgreSQL prima della versione 9.2. A causa del modello MVCC, ogni Tupla nel set di risultati deve essere visitata per verificare la visibilità. Forse stai pensando a Oracle.

Gli sviluppatori PostgreSQL parlano di "scansioni solo indice" . In effetti, la funzione è stata rilasciata con Postgres 9.2. Leggi il messaggio di commit .
Depesz ha scritto un post di blog molto istruttivo .

Gli indici di copertura reali (aggiornamento) sono introdotti con la clausola INCLUDE con Postgres 11. Correlati:

Anche questo è un po 'fuori:

si basa sul fatto che una "scansione completa" di un indice è spesso più rapida di una "scansione completa" della tabella indicizzata a causa delle colonne extra nella tabella che non compaiono nell'indice.

Come riportato nei commenti sull'altra mia risposta, ho anche eseguito dei test con una tabella di due numeri interi e nient'altro. L'indice contiene le stesse colonne della tabella. La dimensione di un indice btree è di circa 2/3 della tabella. Non abbastanza per spiegare un'accelerazione del fattore 3. Ho eseguito più test, in base alla tua configurazione, semplificato su due colonne e con 100000 righe. Nella mia installazione di PostgreSQL 9.0 i risultati sono stati coerenti.

If la tabella ha colonne aggiuntive, l'accelerazione con l'indice diventa più sostanziale, ma questo non è certamente l'unico fattore qui .

Per riassumere i punti principali:

  • Gli indici a più colonne possono essere utilizzati con le query su colonne non iniziali, ma l'accelerazione si basa solo sul fattore 3 per criteri selettivi (piccola percentuale di righe nel risultato). Più in alto per le tuple più grandi, più in basso per le parti più grandi della tabella nel set di risultati.

  • Crea un indice aggiuntivo su quelle colonne se le prestazioni sono importanti.

  • Se tutte le colonne interessate sono incluse in un indice (indice di copertura) e tutte le righe interessate (per blocco) sono visibili a tutte le transazioni, è possibile ottenere "scansione solo indice" in pag 9.2 o successive.

12
  1. Sono equivalenti? Se no allora perché?

    Index (user_id1, user_id2) e Index (user_id2, user_id1)

Questi non sono equivalenti e l'indice in generale (bar, baz) non sarà efficace per le query del modulo select * from foo where baz=?

Erwin ha dimostrato che tali indici possono effettivamente accelerare una query ma questo effetto è limitato e non dello stesso ordine in cui ci si aspetta che un indice migliori una ricerca - si basa sul fatto che un 'pieno la scansione "di un indice è spesso più rapida di una" scansione completa "della tabella indicizzata a causa delle colonne extra nella tabella che non compaiono nell'indice.

Riepilogo: gli indici possono aiutare le query anche su colonne non iniziali, ma in uno dei due modi secondari e relativamente minori e non nel modo drammatico che normalmente ti aspetti che un indice aiuti a causa della sua struttura btree

nb i due modi in cui l'indice può aiutare sono se una scansione completa dell'indice è significativamente più economica di una scansione completa della tabella e sia: 1. le ricerche della tabella sono economiche (perché ce ne sono poche o sono raggruppate), o 2. l'indice è copertura quindi non ci sono ricerche di tabella oops, vedi i commenti di Erwins qui

banco di prova:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

query 1 (nessun indice, colpire 74 buffer ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

query 2 (con indice - l'ottimizzatore ignora l'indice - colpire 74 buffer di nuovo):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

query 2 (con indice - e inganniamo l'ottimizzatore per usarlo):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

Quindi l'accesso tramite l'indice è due volte più veloce in questo caso colpendo 30 buffer - che in termini di indicizzazione è "leggermente più veloce"! E YMMV a seconda di la dimensione relativa di tabella e indice, insieme al numero di righe filtrate e alle caratteristiche di raggruppamento dei dati nella tabella

Al contrario, le query sulla colonna principale fanno uso della struttura btree dell'indice - in questo caso colpire 2 buffer :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms