Indeks (Kun) Scan --> Bitmap Index Scan --> Sekventiel scanning
For få rækker kan det betale sig at køre en indeksscanning. Hvis nok datasider er synlige for alle (=støvsuget nok og ikke for meget samtidig skrivebelastning), og indekset kan give alle nødvendige kolonneværdier, så bruges en hurtigere kun indeksscanning. Med flere rækker, der forventes at blive returneret (højere procentdel af tabellen og afhængigt af datafordeling, værdifrekvenser og rækkebredde) bliver det mere sandsynligt, at der finder flere rækker på én dataside. Så kan det betale sig at skifte til en bitmap-indeksscanning. (Eller at kombinere flere forskellige indekser.) Når en stor procentdel af datasider alligevel skal besøges, er det billigere at køre en sekventiel scanning, filtrere overskydende rækker og helt springe over overhead for indekser.
Indeksbrug bliver (meget) billigere og mere sandsynligt, når adgang til datasider i tilfældig rækkefølge ikke er (meget) dyrere end at få adgang til dem i sekventiel rækkefølge. Det er tilfældet, når du bruger SSD i stedet for at dreje diske, eller endnu mere, jo mere cachelagres i RAM - og de respektive konfigurationsparametre random_page_cost
og effective_cache_size
er indstillet i overensstemmelse hermed.
I dit tilfælde skifter Postgres til en sekventiel scanning og forventer at finde rows=263962
, det er allerede 3 % af hele tabellen. (Mens kun rows=47935
faktisk findes, se nedenfor.)
Mere i dette relaterede svar:
- Effektiv PostgreSQL-forespørgsel på tidsstempel ved hjælp af indeks- eller bitmapindeksscanning?
Pas på med at gennemtvinge forespørgselsplaner
Du kan ikke gennemtvinge en bestemt planlægningsmetode direkte i Postgres, men du kan lave andet metoder virker ekstremt dyre til fejlfindingsformål. Se Planner Method Configuration i manualen.
SET enable_seqscan = off
(som foreslået i et andet svar) gør det ved sekventielle scanninger. Men det er kun beregnet til fejlfindingsformål i din session. Gør ikke brug dette som en generel ramme i produktionen, medmindre du ved præcis, hvad du laver. Det kan fremtvinge latterlige forespørgselsplaner. Manualen:
Disse konfigurationsparametre giver en grov metode til at påvirke de forespørgselsplaner, der er valgt af forespørgselsoptimeringsværktøjet. Hvis standardplanen valgt af optimeringsværktøjet for en bestemt forespørgsel ikke er optimal, er enmidlertidig løsningen er at bruge en af disse konfigurationsparametre til at tvinge optimizeren til at vælge en anden plan. Bedre måder at forbedre kvaliteten af de planer, der er valgt af optimeringsværktøjet, omfatter justering af planlæggerens omkostningskonstanter (se afsnit 19.7.2), kørsel af ANALYZE
manuelt ved at øge værdien af default_statistics_target
konfigurationsparameter og øge mængden af indsamlet statistik for specifikke kolonner ved hjælp af ALTER TABLE SET STATISTICS
.
Det er allerede de fleste af de råd, du har brug for.
- Hold PostgreSQL fra nogle gange at vælge en dårlig forespørgselsplan
I dette særlige tilfælde forventer Postgres 5-6 gange flere hits på email_activities.email_recipient_id
end der faktisk findes:
estimeret rows=227007
vs. actual ... rows=40789
estimeret rows=263962
vs. actual ... rows=47935
Hvis du kører denne forespørgsel ofte, vil det betale sig at have ANALYZE
se på en større prøve for at få mere præcis statistik på den pågældende kolonne. Dit bord er stort (~ 10 mio. rækker), så gør det:
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
Derefter ANALYZE email_activities;
Mål på sidste udvej
I meget sjældent tilfælde kan du ty til at tvinge et indeks med SET LOCAL enable_seqscan = off
i en separat transaktion eller i en funktion med sit eget miljø. Ligesom:
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;
Indstillingen gælder kun for funktionens lokale omfang.
Advarsel: Dette er blot et proof of concept. Selv denne meget mindre radikale manuelle indgriben kan bide dig i det lange løb. Kardinaliteter, værdifrekvenser, dit skema, globale Postgres-indstillinger, alt ændrer sig over tid. Du vil opgradere til en ny Postgres-version. Den forespørgselsplan, du fremtvinger nu, kan blive en meget dårlig idé senere.
Og typisk er dette kun en løsning på et problem med din opsætning. Det er bedre at finde og rette det.
Alternativ forespørgsel
Der mangler væsentlige oplysninger i spørgsmålet, men denne tilsvarende forespørgsel er sandsynligvis hurtigere og mere tilbøjelig til at bruge et indeks på (email_recipient_id
) - i stigende grad for en større LIMIT
.
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);