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

Postgres non utilizza l'indice quando la scansione dell'indice è un'opzione molto migliore

Scansione indice (solo) --> Scansione indice bitmap --> Scansione sequenziale

Per poche righe vale la pena eseguire una scansione dell'indice. Se un numero sufficiente di pagine di dati è visibile a tutti (=abbastanza vuoto e non troppo carico di scrittura simultaneo) e l'indice può fornire tutti i valori di colonna necessari, viene utilizzata una scansione più veloce del solo indice. Con più righe che dovrebbero essere restituite (percentuale più alta della tabella e a seconda della distribuzione dei dati, delle frequenze dei valori e della larghezza delle righe) diventa più probabile trovare più righe in una pagina di dati. Quindi vale la pena passare a una scansione dell'indice bitmap. (O per combinare più indici distinti.) Una volta che una grande percentuale di pagine di dati deve essere comunque visitata, è più economico eseguire una scansione sequenziale, filtrare le righe in eccesso e saltare del tutto l'overhead per gli indici.

L'utilizzo dell'indice diventa (molto) più economico e più probabile quando l'accesso alle pagine di dati in ordine casuale non è (molto) più costoso dell'accesso in ordine sequenziale. Questo è il caso quando si utilizza SSD invece di girare i dischi, o ancora di più, più viene memorizzato nella cache nella RAM e i rispettivi parametri di configurazione random_page_cost e effective_cache_size sono impostati di conseguenza.

Nel tuo caso, Postgres passa a una scansione sequenziale, aspettandosi di trovare rows=263962 , è già il 3% dell'intera tabella. (Mentre solo rows=47935 si trovano effettivamente, vedi sotto.)

Altro in questa risposta correlata:

  • Query PostgreSQL efficiente sul timestamp utilizzando la scansione dell'indice o della bitmap?

Attenzione ai piani di query forzati

Non puoi forzare un determinato metodo di pianificazione direttamente in Postgres, ma puoi crearne altro i metodi sembrano estremamente costosi per scopi di debug. Vedere Configurazione del metodo Planner nel manuale.

SET enable_seqscan = off (come suggerito in un'altra risposta) lo fa alle scansioni sequenziali. Ma questo è inteso solo per scopi di debug nella tua sessione. non usalo come impostazione generale in produzione a meno che tu non sappia esattamente cosa stai facendo. Può forzare piani di query ridicoli. Il manuale:

Questi parametri di configurazione forniscono un metodo grezzo per influenzare i piani di query scelti da Query Optimizer. Se il piano predefinito scelto dall'ottimizzatore per una particolare query non è ottimale, un temporaneo la soluzione consiste nell'utilizzare uno di questi parametri di configurazione per forzare l'ottimizzatore a scegliere un piano diverso. I modi migliori per migliorare la qualità dei piani scelti dall'ottimizzatore includono la regolazione delle costanti di costo del pianificatore (vedere Sezione 19.7.2), l'esecuzione di ANALYZE manualmente, aumentando il valore di default_statistics_target parametro di configurazione e aumentando la quantità di statistiche raccolte per colonne specifiche utilizzando ALTER TABLE SET STATISTICS .

Questa è già la maggior parte dei consigli di cui hai bisogno.

  • Impedisci a PostgreSQL di scegliere a volte un piano di query errato

In questo caso particolare, Postgres si aspetta 5-6 volte più hit su email_activities.email_recipient_id di quelli effettivamente trovati:

stimate rows=227007 rispetto a actual ... rows=40789
stimato rows=263962 rispetto a actual ... rows=47935

Se esegui spesso questa query, sarà conveniente avere ANALYZE guarda un campione più grande per statistiche più accurate sulla colonna particolare. La tua tabella è grande (~ 10 milioni di righe), quindi fallo:

ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000;  -- max 10000, default 100

Quindi ANALYZE email_activities;

Misura di ultima istanza

In molto raro nei casi in cui potresti ricorrere a forzare un indice con SET LOCAL enable_seqscan = off in una transazione separata o in una funzione con il proprio ambiente. Come:

CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
  RETURNS bigint AS
$func$
   SELECT COUNT(DISTINCT a.email_recipient_id)
   FROM   email_activities a
   WHERE  a.email_recipient_id IN (
      SELECT id
      FROM   email_recipients
      WHERE  email_campaign_id = $1
      LIMIT  $2)       -- or consider query below
$func$  LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;

L'impostazione si applica solo all'ambito locale della funzione.

Avviso: Questa è solo una prova di concetto. Anche questo intervento manuale molto meno radicale potrebbe morderti nel lungo periodo. Cardinalità, frequenze di valore, il tuo schema, le impostazioni globali di Postgres, tutto cambia nel tempo. Stai per eseguire l'aggiornamento a una nuova versione di Postgres. Il piano di query che forzi ora potrebbe diventare una pessima idea in seguito.

E in genere questa è solo una soluzione alternativa per un problema con la tua configurazione. Meglio trovarlo e risolverlo.

Richiesta alternativa

Nella domanda mancano informazioni essenziali, ma questa query equivalente è probabilmente più veloce e più probabile che utilizzi un indice su (email_recipient_id ) - sempre più per un LIMIT più grande .

SELECT COUNT(*) AS ct
FROM  (
   SELECT id
   FROM   email_recipients
   WHERE  email_campaign_id = 1607
   LIMIT  43000
   ) r
WHERE  EXISTS (
   SELECT FROM email_activities
   WHERE  email_recipient_id = r.id);