Index (Alleen) Scan --> Bitmap Index Scan --> Sequentiële Scan
Voor een paar rijen loont het om een indexscan uit te voeren. Als er voldoende gegevenspagina's voor iedereen zichtbaar zijn (=genoeg gestofzuigd, en niet te veel gelijktijdige schrijfbelasting) en de index alle benodigde kolomwaarden kan leveren, wordt een snellere alleen-indexscan gebruikt. Met meer rijen die naar verwachting worden geretourneerd (hoger percentage van de tabel en afhankelijk van gegevensdistributie, waardefrequenties en rijbreedte), wordt het waarschijnlijker dat meerdere rijen op één gegevenspagina worden gevonden. Dan loont het om over te schakelen naar een bitmap-indexscans. (Of om meerdere afzonderlijke indexen te combineren.) Zodra een groot percentage gegevenspagina's toch moet worden bezocht, is het goedkoper om een sequentiële scan uit te voeren, overtollige rijen te filteren en de overhead voor indexen helemaal over te slaan.
Indexgebruik wordt (veel) goedkoper en waarschijnlijker wanneer toegang tot datapagina's in willekeurige volgorde niet (veel) duurder is dan toegang in sequentiële volgorde. Dat is het geval bij het gebruik van SSD in plaats van draaiende schijven, of zelfs meer, hoe meer er in het RAM-geheugen wordt opgeslagen - en de respectievelijke configuratieparameters random_page_cost
en effective_cache_size
zijn dienovereenkomstig ingesteld.
In jouw geval schakelt Postgres over op een sequentiële scan, in de verwachting rows=263962
te vinden , dat is al 3% van de hele tafel. (Terwijl alleen rows=47935
daadwerkelijk worden gevonden, zie hieronder.)
Meer in dit gerelateerde antwoord:
- Efficiënte PostgreSQL-query op tijdstempel met index- of bitmap-indexscan?
Pas op voor het forceren van queryplannen
U kunt een bepaalde planner-methode niet rechtstreeks in Postgres forceren, maar u kunt wel other . maken methoden lijken extreem duur voor foutopsporingsdoeleinden. Zie Configuratie plannermethode in de handleiding.
SET enable_seqscan = off
(zoals gesuggereerd in een ander antwoord) doet dat met opeenvolgende scans. Maar dat is alleen bedoeld voor foutopsporingsdoeleinden in uw sessie. Doe niet gebruik dit als een algemene instelling in de productie, tenzij u precies weet wat u doet. Het kan belachelijke queryplannen afdwingen. De handleiding:
Deze configuratieparameters bieden een ruwe methode om de door de query-optimizer gekozen queryplannen te beïnvloeden. Als het standaardplan dat door de optimizer is gekozen voor een bepaalde zoekopdracht niet optimaal is, wordt eentijdelijke oplossing is om een van deze configuratieparameters te gebruiken om de optimizer te dwingen een ander plan te kiezen. Betere manieren om de kwaliteit van de plannen die door de optimizer zijn gekozen te verbeteren, zijn onder meer het aanpassen van de kostenconstanten van de planner (zie paragraaf 19.7.2), het uitvoeren van
ANALYZE
handmatig, de waarde van dedefault_statistics_target
. verhogen configuratieparameter, en het verhogen van de hoeveelheid verzamelde statistieken voor specifieke kolommen met behulp vanALTER TABLE SET STATISTICS
.
Dat is al het meeste advies dat je nodig hebt.
- Voorkom dat PostgreSQL soms een slecht queryplan kiest
In dit specifieke geval verwacht Postgres 5-6 keer meer hits op email_activities.email_recipient_id
dan daadwerkelijk worden gevonden:
geschatte
rows=227007
vs.actual ... rows=40789
geschatterows=263962
vs.actual ... rows=47935
Als u deze zoekopdracht vaak uitvoert, loont het om ANALYZE
. te hebben kijk naar een grotere steekproef voor nauwkeurigere statistieken over de specifieke kolom. Je tafel is groot (~ 10M rijen), dus zorg ervoor dat:
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
Dan ANALYZE email_activities;
Maatregel als laatste redmiddel
In zeer zeldzaam gevallen kunt u een index forceren met SET LOCAL enable_seqscan = off
in een aparte transactie of in een functie met een eigen omgeving. Vind ik leuk:
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;
De instelling is alleen van toepassing op het lokale bereik van de functie.
Waarschuwing: Dit is slechts een proof of concept. Zelfs deze veel minder radicale handmatige interventie kan je op de lange termijn bijten. Kardinaliteiten, waardefrequenties, je schema, globale Postgres-instellingen, alles verandert in de loop van de tijd. U gaat upgraden naar een nieuwe Postgres-versie. Het queryplan dat je nu forceert, kan later een heel slecht idee worden.
En meestal is dit slechts een tijdelijke oplossing voor een probleem met uw installatie. Beter zoeken en repareren.
Alternatieve zoekopdracht
Essentiële informatie ontbreekt in de vraag, maar deze equivalente zoekopdracht is waarschijnlijk sneller en gebruikt eerder een index op (email_recipient_id
) - steeds meer voor een grotere 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);