sql >> Databáze >  >> RDS >> PostgreSQL

Postgres nepoužívá index, když je mnohem lepší volba index scan

Index (pouze) Scan --> Bitmap Index Scan --> Sekvenční skenování

Pro několik řádků se vyplatí spustit indexový sken. Pokud je pro všechny viditelný dostatek datových stránek (=dostatečně vysáté a ne příliš mnoho souběžného zápisu) a index může poskytnout všechny potřebné hodnoty sloupců, pak se použije rychlejší skenování pouze indexu. Očekává-li se, že bude vráceno více řádků (vyšší procento tabulky a v závislosti na distribuci dat, četnosti hodnot a šířce řádku), je pravděpodobnější, že na jedné datové stránce najdete několik řádků. Pak se vyplatí přejít na skenování indexu bitmapy. (Nebo zkombinovat více různých indexů.) Jakmile je nutné velké procento datových stránek stejně navštívit, je levnější spustit sekvenční skenování, filtrovat přebytečné řádky a úplně přeskočit režii indexů.

Používání indexu je (mnohem) levnější a pravděpodobnější, když přístup k datovým stránkám v náhodném pořadí není (mnohem) dražší než přístup k nim v sekvenčním pořadí. To je případ, kdy místo rotujících disků používáte SSD, nebo ještě více, čím více je uloženo v paměti RAM – a příslušné konfigurační parametry random_page_cost a effective_cache_size jsou nastaveny podle toho.

Ve vašem případě se Postgres přepne na sekvenční skenování a očekává, že najde rows=263962 , to jsou již 3 % z celé tabulky. (Zatímco pouze rows=47935 jsou skutečně nalezeny, viz níže.)

Více v této související odpovědi:

  • Efektivní PostgreSQL dotaz na časové razítko pomocí indexového nebo bitmapového indexového skenování?

Dejte si pozor na vynucování plánů dotazů

Určitou metodu plánovače nemůžete vynutit přímo v Postgresu, ale můžete vytvořit jinou metody se zdají extrémně drahé pro účely ladění. Viz Konfigurace metody plánovače v příručce.

SET enable_seqscan = off (jak je navrženo v jiné odpovědi) to dělá sekvenčnímu skenování. Ale to je určeno pouze pro účely ladění ve vaší relaci. nedělejte to použijte toto jako obecné nastavení ve výrobě, pokud přesně nevíte, co děláte. Může si vynutit směšné plány dotazů. Manuál:

Tyto konfigurační parametry poskytují hrubou metodu ovlivňování plánů dotazů zvolených optimalizátorem dotazů. Pokud výchozí plán zvolený optimalizátorem pro konkrétní dotaz není optimální, dočasné řešením je použít jeden z těchto konfiguračních parametrů k přinucení optimalizátoru zvolit jiný plán. Lepší způsoby, jak zlepšit kvalitu plánů zvolených optimalizátorem, zahrnují úpravu nákladových konstant plánovače (viz oddíl 19.7.2), spuštění ANALYZE ručně, zvýšením hodnoty default_statistics_target konfigurační parametr a zvýšení množství statistik shromážděných pro konkrétní sloupce pomocí ALTER TABLE SET STATISTICS .

To už je většina rad, které potřebujete.

  • Zabraňte PostgreSQL, aby někdy zvolil špatný plán dotazů

V tomto konkrétním případě Postgres očekává 5-6krát více přístupů na email_activities.email_recipient_id než jsou ve skutečnosti nalezeny:

odhadovaný rows=227007 vs. actual ... rows=40789
odhadovaný rows=263962 vs. actual ... rows=47935

Pokud budete tento dotaz spouštět často, vyplatí se mít ANALYZE podívejte se na větší vzorek pro přesnější statistiky pro konkrétní sloupec. Váš stůl je velký (~ 10 milionů řádků), takže:

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

Poté ANALYZE email_activities;

Opatření poslední instance

velmi vzácné v případech se můžete uchýlit k vynucení indexu pomocí SET LOCAL enable_seqscan = off v samostatné transakci nebo ve funkci s vlastním prostředím. Jako:

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;

Nastavení se vztahuje pouze na místní rozsah funkce.

Upozornění: Toto je jen důkaz konceptu. I tento mnohem méně radikální manuální zásah vás může z dlouhodobého hlediska kousnout. Mohutnosti, hodnotové frekvence, vaše schéma, globální nastavení Postgresu, vše se v čase mění. Chystáte se upgradovat na novou verzi Postgres. Plán dotazů, který si vynutíte nyní, se později může stát velmi špatným nápadem.

A obvykle se jedná pouze o řešení problému s nastavením. Je lepší to najít a opravit.

Alternativní dotaz

V otázce chybí základní informace, ale tento ekvivalentní dotaz je pravděpodobně rychlejší a pravděpodobněji použije index na (email_recipient_id ) – čím dál tím více pro větší 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. CASCADE DELETE pouze jednou

  2. MySQL rychle odstraňuje duplikáty z velké databáze

  3. Jak získat dotaz na atributy sloupců z názvu tabulky pomocí PostgreSQL?

  4. OPRAVA:MySQL – příkaz SELECT odepřen uživateli