Indice non utilizzato, ma influenza la query


8

Ho una tabella PostgreSQL 9.3 con alcuni numeri e alcuni dati aggiuntivi:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Questa tabella contiene attualmente circa 10 milioni di record e occupa 1 GB di spazio su disco. myidnon sono consecutivi.

Voglio calcolare quante righe ci sono in ogni blocco di 100000 numeri consecutivi:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Ciò restituisce circa 3500 righe.

Ho notato che l'esistenza di un certo indice accelera notevolmente questa query anche se il piano di query non lo menziona affatto. Il piano di query senza l'indice:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

L'indice:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

Il nuovo piano di query:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Pertanto, i piani di query e i tempi di esecuzione differiscono in modo significativo (quasi tre volte) ma non menzionano né l'indice. Questo comportamento è perfettamente riproducibile sulla mia macchina di sviluppo: ho attraversato diversi cicli di rilascio dell'indice, testando la query più volte, ricreando l'indice, testando nuovamente la query più volte. Cosa sta succedendo qui?


Non sono un esperto dell'analisi dei piani di query di Postgres, ma suppongo che l'indice sia utilizzato per il HashAggregatemetodo (e non è richiesto alcun ordinamento), in modo da ottenere prestazioni migliori. Perché l'indice non è menzionato nel piano, non ne ho idea.
ypercubeᵀᴹ

Fa l'uscita del cambio piano, se si attiva la modalità dettagliata utilizzando: explain (analyze true, verbose true) ...?
a_horse_with_no_name

Sarebbe bello se si potesse ridurre questo in un caso di test autonomo. Sembra sicuramente strano.
Craig Ringer,

@a_horse_with_no_name: Sì, cambia: ho sostituito i piani di query con quelli dettagliati nella domanda. Ma quel piano di query non menziona ancora affatto l'indice.
liori,

Se ci sono più statistiche disponibili (specialmente cardinalità e possibilmente valori min / max) sulla colonna id con l'indice che senza, ciò potrebbe cambiare il gruppo dell'ottimizzatore in base alla selezione del metodo, anche se non finisce per usare l'indice . (Non conosco affatto l'ottimizzatore e le statistiche di Postgres, quindi non ho idea se questo potrebbe essere il caso o meno.)
Mat

Risposte:


3

VACUUM ANALYZEfa la differenza nel tuo esempio. Inoltre, come fornito da @jjanes , le statistiche aggiuntive per l'indice funzionale. Per documentazione:

pg_statisticmemorizza anche dati statistici sui valori delle espressioni di indice. Questi sono descritti come se fossero colonne di dati reali; in particolare, fa starelidriferimento all'indice. Tuttavia, non viene effettuata alcuna voce per una normale colonna di indice di non espressione, poiché sarebbe ridondante con la voce per la colonna della tabella sottostante.

Tuttavia, la creazione dell'indice non comporta di per sé la raccolta di statistiche da parte di Postgres. Provare:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Non restituisce nulla fino a quando non esegui il tuo primo ANALYZE(o VACUUM ANALYZE, o il demone autovacuum inizia).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Ora vedrai le statistiche aggiunte.

Poiché l'intera tabella deve essere letta comunque, Postgres utilizzerà una scansione sequenziale a meno che non si aspetti che il calcolo myid/100000sia abbastanza costoso da passare, cosa che non è.

L'unica altra possibilità sarebbe una scansione solo dell'indice se l'indice è molto più piccolo della tabella - e sono soddisfatte le condizioni preliminari per una scansione solo dell'indice. Dettagli nel Postgres Wiki e nel manuale .

Finché tale indice funzionale non viene utilizzato, il vantaggio collaterale derivante dalle statistiche aggiunte è moderato. Se la tabella fosse di sola lettura il costo sarebbe basso, ma di nuovo probabilmente vedremmo subito una scansione di solo indice.

Forse puoi anche ottenere piani di query migliori impostando un obiettivo statistico più elevato per mytable.myid. Ciò comporterebbe solo un costo minore. Di Più:


Grazie per questa spiegazione, è molto utile per comprendere il problema. Nel mio caso probabilmente avrò bisogno di una myid/100000 BETWEEN somevalue AND othervaluecondizione aggiuntiva , quindi l'indice verrà comunque utilizzato nel piano di query: ho appena fatto questa domanda perché non ho capito perché l'indice sia utile nel caso di tutta la tabella.
liori,

@liori: si potrebbe coprire che con WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(si pensi arrotondamento effetti a seconda delle tipologie), e probabilmente già avere un indice pianura myid, in modo da poter fare a meno di un indice specializzato supplementare. Potrebbe essere più efficiente.
Erwin Brandstetter,

6

Quando si crea un indice di espressione, PostgreSQL fa raccogliere statistiche sull'espressione. Con queste statistiche a portata di mano, ora ha una stima accurata del numero di righe aggregate che la query restituirà, il che la porta a fare una scelta migliore per il piano.

In particolare, in questo caso, senza quelle statistiche extra pensava che la tabella hash sarebbe troppo grande per adattarsi a work_mem, quindi non ha scelto quel metodo.


Penso che il pianificatore non work_memtenga conto del valore di . Se lo hai sollevato in modo che l'ordinamento si adatti alla memoria, se utilizzerebbe comunque lo stesso piano. Vorrei sottolineare che la differenza di tempo (la maggior parte) deriva dall'ordinamento del disco esterno.
dezso,

1
@dezso Che cosa succede se si raddoppia o triplica sperimentalmente il valore di work_mem necessario per adattare il tipo in memoria? L'ordinamento e l'hashing hanno diverse stime generali e le stime stesse non sono molto precise. Inoltre, quale versione secondaria di 9.3 stai usando?
jjanes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.