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

Postgres bruger ikke indeks, når indeksscanning er meget bedre mulighed

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


  1. Sådan aktiverer du MySQL Slow Query Log på MySQL

  2. Overtrædelse af UNIQUE KEY-begrænsning på INSERT WHERE COUNT(*) =0 på SQL Server 2005

  3. Sådan opdaterer du en kolonne baseret på et filter af en anden kolonne

  4. PHP (MySQL) fejl:Advarsel:mysql_num_rows() forventer, at parameter 1 er ressource