Come costringo Postgres a utilizzare un determinato indice?


112

Come costringo Postgres a utilizzare un indice quando altrimenti insisterebbe per eseguire una scansione sequenziale?



1
+1 Mi piacerebbe vedere questa funzione. Non si tratta semplicemente di disabilitare la scansione seq, come dicono altre risposte: abbiamo bisogno della capacità di forzare PG a utilizzare un indice specifico . Questo perché nella parola reale le statistiche possono essere completamente sbagliate ea quel punto è necessario utilizzare soluzioni alternative inaffidabili / parziali. Sono d'accordo che in casi semplici dovresti prima controllare gli indici e altre impostazioni, ma per affidabilità e usi avanzati sui big data abbiamo bisogno di questo.
collimarco

MySQL e Oracle ce l'hanno entrambi ... Non so perché il pianificatore di Postgres sia così inaffidabile.
Kevin Parker

Risposte:


103

Supponendo che tu stia chiedendo informazioni sulla comune funzionalità di "suggerimento indice" che si trova in molti database, PostgreSQL non fornisce tale funzionalità. Questa è stata una decisione consapevole presa dal team di PostgreSQL. Una buona panoramica del perché e di cosa puoi fare invece può essere trovata qui . Le ragioni sono fondamentalmente che si tratta di un hack delle prestazioni che tende a causare più problemi in seguito quando i dati cambiano, mentre l'ottimizzatore di PostgreSQL può rivalutare il piano in base alle statistiche. In altre parole, quello che oggi potrebbe essere un buon piano di query probabilmente non sarà un buon piano di query per sempre e i suggerimenti sull'indice forzano un particolare piano di query per sempre.

Come un martello molto smussato, utile per i test, puoi usare i parametri enable_seqscane enable_indexscan. Vedere:

Questi non sono adatti per l'uso in produzione continua . In caso di problemi con la scelta del piano di query, dovresti consultare la documentazione per individuare i problemi di prestazioni delle query . Non enable_limitarti a impostare i parametri e andare via.

A meno che tu non abbia una buona ragione per usare l'indice, Postgres potrebbe fare la scelta corretta. Perché?

  • Per i tavoli piccoli, è più veloce eseguire scansioni sequenziali.
  • Postgres non utilizza gli indici quando i tipi di dati non corrispondono correttamente, potrebbe essere necessario includere cast appropriati.
  • Le impostazioni del tuo pianificatore potrebbero causare problemi.

Vedi anche questo vecchio post di newsgroup .


4
D'accordo, costringere postgres a farlo a modo tuo di solito significa che hai sbagliato. 9/10 volte il pianificatore batterà qualsiasi cosa tu possa inventare. L'altra volta è perché hai sbagliato.
Kent Fredric

Penso che sia una buona idea controllare realmente le classi di operatori del tuo indice.
metdos

2
Odio far rivivere una vecchia domanda ma vedo spesso nella documentazione di Postgres, discussioni e qui, ma esiste un concetto generalizzato per ciò che si qualifica per un tavolino ? È qualcosa come 5000 righe o 50000 ecc.?
waffl

1
@waffl Hai considerato il benchmarking? Crea una semplice tabella con un indice e una funzione di accompagnamento per riempirla con n righe di spazzatura casuale. Quindi inizia a guardare il piano di query per diversi valori di n . Quando vedi che inizia a usare l'indice, dovresti avere una risposta approssimativa. Puoi anche ottenere scansioni sequenziali se PostgreSQL determina (in base alle statistiche) che una scansione dell'indice non eliminerà anche molte righe. Quindi il benchmarking è sempre una buona idea quando hai problemi di prestazioni reali. Come ipotesi improvvisa e aneddotica, direi che un paio di migliaia di solito sono "piccoli".
jpmc26

11
Con oltre 30 anni di esperienza su piattaforme come Oracle, Teradata e MSSQL, trovo l'ottimizzatore di PostgreSQL 10 non particolarmente intelligente. Anche con statistiche aggiornate genera piani di esecuzione meno efficienti rispetto a quelli forzati in una direzione speciale. Fornire suggerimenti strutturali per compensare questi problemi fornirebbe una soluzione per consentire a PostgreSQL di crescere in più segmenti di mercato. A PARER MIO.
Guido Leenders

75

Probabilmente l'unico motivo valido per l'utilizzo

set enable_seqscan=false

è quando scrivi query e vuoi vedere rapidamente quale sarebbe effettivamente il piano di query se ci fossero grandi quantità di dati nelle tabelle. O, naturalmente, se è necessario confermare rapidamente che la query non utilizza un indice semplicemente perché il set di dati è troppo piccolo.


41
questa breve risposta in realtà fornisce un buon suggerimento a scopo di test
dwery

3
Nessuno sta rispondendo alla domanda!
Ivailo Bardarov

@IvailoBardarov Il motivo per cui tutti questi altri suggerimenti sono qui è perché PostgreSQL non ha questa caratteristica; questa è stata una decisione consapevole presa dagli sviluppatori in base a come viene tipicamente utilizzato e ai problemi a lungo termine che provoca.
jpmc26

Un bel trucco da testare: esegui set enable_seqscan=false, esegui la tua query e poi corri velocemente set enable_seqscan=trueper riportare postgresql al suo comportamento corretto (e ovviamente non farlo in produzione, solo in fase di sviluppo!)
Brian Hellekin

2
@BrianHellekin Meglio, SET SESSION enable_seqscan=falseper influenzare solo te stesso
Izkata

20

A volte PostgreSQL non riesce a fare la scelta migliore di indici per una particolare condizione. Ad esempio, supponiamo che ci sia una tabella delle transazioni con diversi milioni di righe, di cui ce ne sono diverse centinaia per un dato giorno, e la tabella ha quattro indici: transaction_id, client_id, date e description. Si desidera eseguire la seguente query:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQL può scegliere di utilizzare l'indice transaction_description_idx invece di transaction_date_idx, il che può portare la query a impiegare diversi minuti invece di meno di un secondo. Se questo è il caso, puoi forzare l'utilizzo dell'indice alla data eludendo la condizione in questo modo:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id

3
Bella idea. Tuttavia, quando disabilitiamo l'utilizzo corrente dell'indice con questo metodo, l'ottimizzatore di query postgresql esegue il fallback al successivo indice appropriato. Pertanto, nessuna garanzia che l'ottimizzatore sceglierà your_wanted_index, può essere così che il motore postgresql eseguirà invece una scansione della sequenza / chiave primaria. Conclusione: non esiste un metodo affidabile al 100% per forzare l'utilizzo degli indici per il server PostgreSql.
Agnius Vasiliauskas

Cosa succede se non ci sono wherecondizioni ma due tabelle o unite e Postgres non riesce a prendere l'indice.
Luna Lovegood

@Surya quanto sopra si applica sia a WHERE che a JOIN ... ON condizioni
Ziggy Crueltyfree Zeitgeister

18

Risposta breve

Questo problema si verifica in genere quando il costo stimato di una scansione dell'indice è troppo alto e non riflette correttamente la realtà. Potrebbe essere necessario abbassare il random_page_costparametro di configurazione per risolvere questo problema. Dalla documentazione di Postgres :

La riduzione di questo valore [...] farà sì che il sistema preferisca le scansioni dell'indice; alzarlo farà sembrare le scansioni dell'indice relativamente più costose.

Puoi verificare se un valore inferiore farà effettivamente utilizzare a Postgres l'indice (ma usalo solo per i test ):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

È possibile ripristinare SET random_page_cost = DEFAULT;nuovamente il valore predefinito con .

sfondo

Le scansioni degli indici richiedono il recupero delle pagine del disco non sequenziale. Postgres utilizza random_page_costper stimare il costo di tali recuperi non sequenziali in relazione ai recuperi sequenziali. Il valore predefinito è 4.0, quindi supponendo un fattore di costo medio di 4 rispetto ai recuperi sequenziali (tenendo conto degli effetti della cache).

Il problema tuttavia è che questo valore predefinito non è adatto nei seguenti importanti scenari di vita reale:

1) Unità a stato solido

Come ammette la documentazione:

Lo storage che ha un basso costo di lettura casuale rispetto alle unità sequenziali, ad esempio unità a stato solido, potrebbe essere modellato meglio con un valore inferiore per random_page_cost.

Secondo l'ultimo punto di questa diapositiva da un intervento al PostgresConf 2018, random_page_costdovrebbe essere impostato su qualcosa tra 1.0e 2.0per le unità a stato solido.

2) Dati memorizzati nella cache

Se i dati dell'indice richiesti sono già memorizzati nella cache della RAM, una scansione dell'indice sarà sempre molto più veloce di una scansione sequenziale. La documentazione dice:

Di conseguenza, se è probabile che i tuoi dati siano completamente nella cache, [...] random_page_costpuò essere appropriato ridurli.

Il problema è che ovviamente non puoi sapere facilmente se i dati rilevanti sono già memorizzati nella cache. Tuttavia, se viene richiesto frequentemente un indice specifico e se il sistema dispone di RAM sufficiente, è probabile che i dati vengano memorizzati nella cache erandom_page_cost dovrebbero essere impostati su un valore inferiore. Dovrai sperimentare valori diversi e vedere cosa funziona per te.

Potresti anche voler usare l' estensione pg_prewarm per la cache dei dati esplicita.



2
Ho anche dovuto impostare random_page_cost = 0.1 per far funzionare la scansione dell'indice su una tabella di grandi dimensioni (~ 600 milioni di righe) in Pg 10.1 su Ubuntu. Senza il tweak, la scansione seq (nonostante fosse parallela) richiedeva 12 minuti (nota che è stata eseguita la tabella di analisi!). L'unità è SSD. Dopo il tweak, il tempo di esecuzione è diventato 1 secondo.
Anatoly Alekseev

Mi hai salvato la giornata. Stavo impazzendo cercando di capire come la stessa identica query sullo stesso database impiegasse 30 secondi su una macchina e meno di 1 su un'altra, anche dopo aver eseguito l'analisi su entrambe le estremità ... A chi può interessare: il comando ' ALTER SYSTEM SET random_page_cost = x 'imposta il nuovo valore predefinito a livello globale.
Julien

10

La domanda di per sé è decisamente invalida. Forzare (facendo enable_seqscan = off per esempio) è una pessima idea. Potrebbe essere utile verificare se sarà più veloce, ma il codice di produzione non dovrebbe mai usare questi trucchi.

Invece, spiega l'analisi della tua query, leggila e scopri perché PostgreSQL sceglie un piano (secondo te) sbagliato.

Ci sono strumenti sul web che aiutano a leggere, spiegare, analizzare l'output - uno di questi è explore.depesz.com - scritto da me.

Un'altra opzione è quella di entrare nel canale #postgresql sulla rete irc freenode e parlare con i ragazzi lì per aiutarti - poiché l'ottimizzazione della query non è una questione di "fai una domanda, ricevi una risposta sii felice". è più come una conversazione, con molte cose da controllare, molte cose da imparare.


2

C'è un trucco per spingere postgres a preferire un seqscan che aggiunge un OFFSET 0nella sottoquery

Questo è utile per ottimizzare le richieste che collegano tabelle grandi / enormi quando tutto ciò di cui hai bisogno sono solo gli n primi / ultimi elementi.

Diciamo che stai cercando i primi / ultimi 20 elementi che coinvolgono più tabelle con 100k (o più) voci, non ha senso costruire / collegare tutte le query su tutti i dati quando ciò che stai cercando è nei primi 100 o 1000 inserimenti. In questo scenario, ad esempio, risulta essere oltre 10 volte più veloce per eseguire una scansione sequenziale.

vedi Come posso impedire a Postgres di integrare una sottoquery?


Bel trucco. Anche se un buon ottimizzatore dovrebbe ovviamente ottimizzare l'offset 0 :-)
Guido Leenders
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.