PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres verwendet keinen Index, wenn der Index-Scan eine viel bessere Option ist

Index-Scan (nur) --> Bitmap-Index-Scan --> Sequenzieller Scan

Bei wenigen Zeilen lohnt es sich, einen Index-Scan durchzuführen. Wenn genügend Datenseiten für alle sichtbar sind (=ausreichend evakuiert und nicht zu viel gleichzeitige Schreiblast) und der Index alle erforderlichen Spaltenwerte bereitstellen kann, wird ein schnellerer Nur-Index-Scan verwendet. Je mehr Zeilen zurückgegeben werden (höherer Prozentsatz der Tabelle und je nach Datenverteilung, Werthäufigkeit und Zeilenbreite), desto wahrscheinlicher ist es, dass mehrere Zeilen auf einer Datenseite gefunden werden. Dann lohnt es sich, auf einen Bitmap-Indexscan umzusteigen. (Oder um mehrere unterschiedliche Indizes zu kombinieren.) Sobald ein großer Prozentsatz von Datenseiten sowieso besucht werden muss, ist es billiger, einen sequenziellen Scan durchzuführen, überschüssige Zeilen zu filtern und den Overhead für Indizes insgesamt zu überspringen.

Die Indexnutzung wird (viel) billiger und wahrscheinlicher, wenn der Zugriff auf Datenseiten in zufälliger Reihenfolge nicht (viel) teurer ist als der Zugriff auf sie in sequentieller Reihenfolge. Das ist der Fall, wenn SSD anstelle von sich drehenden Festplatten verwendet wird, oder umso mehr, je mehr im RAM zwischengespeichert wird - und die entsprechenden Konfigurationsparameter random_page_cost und effective_cache_size entsprechend gesetzt.

In Ihrem Fall wechselt Postgres zu einem sequentiellen Scan und erwartet, rows=263962 zu finden , das sind schon 3 % der gesamten Tabelle. (Während nur rows=47935 tatsächlich gefunden werden, siehe unten.)

Mehr in dieser verwandten Antwort:

  • Effiziente PostgreSQL-Abfrage des Zeitstempels mit Index- oder Bitmap-Index-Scan?

Achten Sie darauf, Abfragepläne zu erzwingen

Sie können eine bestimmte Planermethode nicht direkt in Postgres erzwingen, aber Sie können andere erstellen Methoden scheinen für Debugging-Zwecke extrem teuer zu sein. Siehe Konfiguration der Planer-Methode im Handbuch.

SET enable_seqscan = off (wie in einer anderen Antwort vorgeschlagen) macht das bei sequentiellen Scans. Aber das ist nur für Debugging-Zwecke in Ihrer Sitzung gedacht. nicht Verwenden Sie dies als allgemeine Einstellung in der Produktion, es sei denn, Sie wissen genau, was Sie tun. Es kann lächerliche Abfragepläne erzwingen. Das Handbuch:

Diese Konfigurationsparameter bieten eine grobe Methode zur Beeinflussung der vom Abfrageoptimierer ausgewählten Abfragepläne. Wenn der vom Optimierer für eine bestimmte Abfrage gewählte Standardplan nicht optimal ist, wird einvorübergehender Die Lösung besteht darin, einen dieser Konfigurationsparameter zu verwenden, um den Optimierer zu zwingen, einen anderen Plan auszuwählen. Bessere Möglichkeiten, die Qualität der vom Optimierer ausgewählten Pläne zu verbessern, umfassen das Anpassen der Kostenkonstanten des Planers (siehe Abschnitt 19.7.2), das Ausführen von ANALYZE manuell, indem Sie den Wert von default_statistics_target erhöhen Konfigurationsparameter und Erhöhen der Menge an Statistiken, die für bestimmte Spalten gesammelt werden, mithilfe von ALTER TABLE SET STATISTICS .

Das sind bereits die meisten Ratschläge, die Sie benötigen.

  • Halten Sie PostgreSQL davon ab, manchmal einen schlechten Abfrageplan zu wählen

In diesem speziellen Fall erwartet Postgres 5-6 Mal mehr Zugriffe auf email_activities.email_recipient_id als tatsächlich gefunden werden:

geschätzte rows=227007 vs. actual ... rows=40789
geschätzt rows=263962 vs. actual ... rows=47935

Wenn Sie diese Abfrage oft ausführen, zahlt es sich aus, ANALYZE zu haben Sehen Sie sich eine größere Stichprobe an, um genauere Statistiken zu der jeweiligen Spalte zu erhalten. Ihre Tabelle ist groß (~ 10 Millionen Zeilen), also machen Sie das:

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

Dann ANALYZE email_activities;

Maßnahme des letzten Auswegs

In sehr selten In einigen Fällen können Sie darauf zurückgreifen, einen Index mit SET LOCAL enable_seqscan = off zu erzwingen in einer separaten Transaktion oder in einer Funktion mit eigener Umgebung. Wie:

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;

Die Einstellung gilt nur für den lokalen Geltungsbereich der Funktion.

Warnung: Dies ist nur ein Proof of Concept. Auch dieser viel weniger radikale manuelle Eingriff könnte Sie auf Dauer beißen. Kardinalitäten, Werthäufigkeiten, Ihr Schema, globale Postgres-Einstellungen, alles ändert sich im Laufe der Zeit. Sie werden auf eine neue Postgres-Version upgraden. Der Abfrageplan, den Sie jetzt erzwingen, kann später zu einer sehr schlechten Idee werden.

Und normalerweise ist dies nur eine Problemumgehung für ein Problem mit Ihrer Einrichtung. Finden und reparieren Sie es besser.

Alternative Abfrage

In der Frage fehlen wesentliche Informationen, aber diese äquivalente Abfrage ist wahrscheinlich schneller und verwendet eher einen Index für (email_recipient_id ) - zunehmend für ein größeres 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);