sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgres använder inte index när indexskanning är ett mycket bättre alternativ

Index (endast) Scan --> Bitmap Index Scan --> Sekventiell skanning

För några rader lönar det sig att köra en indexskanning. Om tillräckligt många datasidor är synliga för alla (=tillräckligt vakuum och inte för mycket samtidig skrivbelastning) och indexet kan tillhandahålla alla kolumnvärden som behövs, används en snabbare endast indexskanning. Med fler rader som förväntas returneras (högre procentandel av tabellen och beroende på datadistribution, värdefrekvenser och radbredd) blir det mer sannolikt att hitta flera rader på en datasida. Då lönar det sig att byta till en bitmappsindexskanning. (Eller för att kombinera flera distinkta index.) När en stor andel av datasidorna ändå måste besökas, är det billigare att köra en sekventiell skanning, filtrera överskottsrader och hoppa över omkostnader för index helt och hållet.

Indexanvändning blir (mycket) billigare och mer sannolikt när det inte är (mycket) dyrare att komma åt datasidor i slumpmässig ordning än att komma åt dem i sekventiell ordning. Det är fallet när du använder SSD istället för att snurra diskar, eller ännu mer så desto mer cachelagras i RAM - och respektive konfigurationsparametrar random_page_cost och effective_cache_size är inställda därefter.

I ditt fall byter Postgres till en sekventiell skanning och förväntar sig att hitta rows=263962 , det är redan 3 % av hela tabellen. (Medan endast rows=47935 faktiskt hittas, se nedan.)

Mer i detta relaterade svar:

  • Effektiv PostgreSQL-fråga på tidsstämpel med index- eller bitmappsindexskanning?

Se upp för att tvinga fram frågeplaner

Du kan inte tvinga fram en viss planeringsmetod direkt i Postgres, men du kan göra annan metoder verkar extremt dyra för felsökningsändamål. Se Planer Method Configuration i manualen.

SET enable_seqscan = off (som föreslås i ett annat svar) gör det med sekventiella skanningar. Men det är endast avsett för felsökningsändamål i din session. Gör inte använd detta som en allmän inställning i produktionen om du inte vet exakt vad du gör. Det kan tvinga fram löjliga frågeplaner. Manualen:

Dessa konfigurationsparametrar ger en grov metod för att påverka frågeplanerna som valts av frågeoptimeraren. Om standardplanen som valts av optimeraren för en viss fråga inte är optimal, entillfällig lösningen är att använda en av dessa konfigurationsparametrar för att tvinga optimeraren att välja en annan plan. Bättre sätt att förbättra kvaliteten på planerna som väljs av optimeraren inkluderar att justera planerarens kostnadskonstanter (se avsnitt 19.7.2), att köra ANALYZE manuellt, öka värdet på default_statistics_target konfigurationsparameter, och öka mängden statistik som samlas in för specifika kolumner med ALTER TABLE SET STATISTICS .

Det är redan de flesta råd du behöver.

  • Få PostgreSQL från att ibland välja en dålig frågeplan

I det här specifika fallet förväntar sig Postgres 5-6 gånger fler träffar på email_activities.email_recipient_id än som faktiskt hittas:

uppskattad rows=227007 kontra actual ... rows=40789
uppskattad rows=263962 kontra actual ... rows=47935

Om du kör den här frågan ofta kommer det att löna sig att ha ANALYZE titta på ett större urval för mer exakt statistik på den specifika kolumnen. Ditt bord är stort (~ 10 miljoner rader), så gör det:

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

Sedan ANALYZE email_activities;

Åtgärd för sista utväg

I mycket sällsynt fall kan du tvinga fram ett index med SET LOCAL enable_seqscan = off i en separat transaktion eller i en funktion med egen miljö. Gilla:

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;

Inställningen gäller endast för funktionens lokala omfattning.

Varning: Detta är bara ett bevis på konceptet. Även detta mycket mindre radikala manuella ingrepp kan bita dig i det långa loppet. Kardinaliteter, värdefrekvenser, ditt schema, globala Postgres-inställningar, allt förändras över tiden. Du kommer att uppgradera till en ny Postgres-version. Frågeplanen du tvingar fram nu kan bli en mycket dålig idé senare.

Och vanligtvis är detta bara en lösning för ett problem med din installation. Bättre att hitta och fixa det.

Alternativ fråga

Viktig information saknas i frågan, men denna likvärdiga fråga är förmodligen snabbare och mer sannolikt att använda ett index på (email_recipient_id ) - alltmer för 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);


  1. Vilka är stegen i databasdesign?

  2. Överträdelse av integritetsbegränsning:1452 Kan inte lägga till eller uppdatera en underordnad rad:

  3. Topp 7 jobb som kräver SQL

  4. 4 Fantastiska SQL Server-övervakningsresurser för databasadministratörer