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);