Wie zwinge ich Postgres, einen bestimmten Index zu verwenden?

111

Wie zwinge ich Postgres, einen Index zu verwenden, wenn es sonst darauf bestehen würde, einen sequentiellen Scan durchzuführen?

Mikrofon
quelle
Dupliziert, siehe stackoverflow.com/questions/14554302/…
Grigory Kislin
1
+1 Ich würde diese Funktion gerne sehen. Es geht nicht nur darum, den seq-Scan zu deaktivieren, wie andere Antworten sagen: Wir müssen PG zwingen können, einen bestimmten Index zu verwenden . Dies liegt daran, dass Statistiken im wirklichen Wort völlig falsch sein können und Sie an diesem Punkt unzuverlässige / teilweise Problemumgehungen verwenden müssen. Ich bin damit einverstanden, dass Sie in einfachen Fällen zuerst die Indizes und andere Einstellungen überprüfen sollten, aber für Zuverlässigkeit und erweiterte Verwendung von Big Data benötigen wir dies.
Collimarco
MySQL und Oracle haben es beide ... Ich bin mir nicht sicher, warum der Planer von Postgres so unzuverlässig ist.
Kevin Parker

Antworten:

103

Angenommen, Sie fragen nach der allgemeinen Funktion "Indexhinweise", die in vielen Datenbanken zu finden ist, bietet PostgreSQL eine solche Funktion nicht an. Dies war eine bewusste Entscheidung des PostgreSQL-Teams. Eine gute Übersicht darüber, warum und was Sie stattdessen tun können, finden Sie hier . Die Gründe dafür sind im Grunde, dass es sich um einen Performance-Hack handelt, der später zu weiteren Problemen führt, wenn sich Ihre Daten ändern, während der Optimierer von PostgreSQL den Plan basierend auf den Statistiken neu bewerten kann. Mit anderen Worten, was heute ein guter Abfrageplan sein könnte, wird wahrscheinlich nicht für alle Zeiten ein guter Abfrageplan sein, und Indexhinweise erzwingen einen bestimmten Abfrageplan für alle Zeiten.

Als sehr stumpfer Hammer, der zum Testen nützlich ist, können Sie die Parameter enable_seqscanund verwenden enable_indexscan. Sehen:

Diese sind nicht für den laufenden Produktionseinsatz geeignet . Wenn Sie Probleme mit der Auswahl des Abfrageplans haben, sollten Sie die Dokumentation zum Aufspüren von Problemen mit der Abfrageleistung lesen . enable_Stellen Sie nicht nur Parameter ein und gehen Sie weg.

Wenn Sie keinen guten Grund für die Verwendung des Index haben, trifft Postgres möglicherweise die richtige Wahl. Warum?

  • Bei kleinen Tabellen ist es schneller, sequentielle Scans durchzuführen.
  • Postgres verwendet keine Indizes, wenn die Datentypen nicht richtig übereinstimmen. Möglicherweise müssen Sie entsprechende Casts einfügen.
  • Ihre Planereinstellungen können Probleme verursachen.

Siehe auch diesen alten Newsgroup-Beitrag .

Patryk Kordylewski
quelle
4
Einverstanden: Wenn Sie Postgres dazu zwingen, es auf Ihre Weise zu tun, bedeutet dies normalerweise, dass Sie es falsch gemacht haben. 9/10 Mal schlägt der Planer alles, was Sie sich einfallen lassen können. Das andere Mal ist es, weil du es falsch gemacht hast.
Kent Fredric
Ich denke, es ist eine gute Idee, um wirklich Operatorklassen Ihres Index-Hold zu überprüfen.
Metdos
2
Ich hasse es, eine alte Frage wiederzubeleben, aber ich sehe sie oft in Postgres-Dokumentationen, Diskussionen und hier, aber gibt es ein allgemeines Konzept für das, was für einen kleinen Tisch geeignet ist ? Ist es so etwas wie 5000 Zeilen oder 50000 usw.?
Waffel
1
@waffl Hast du über Benchmarking nachgedacht? Erstellen Sie eine einfache Tabelle mit einem Index und einer zugehörigen Funktion, um sie mit n Zeilen zufälligen Mülls zu füllen . Schauen Sie sich dann den Abfrageplan nach verschiedenen Werten von n an . Wenn Sie sehen, dass der Index verwendet wird, sollten Sie eine Standardantwort haben. Sie können auch sequentielle Scans erhalten, wenn PostgreSQL (basierend auf Statistiken) feststellt, dass bei einem Index-Scan nicht auch sehr viele Zeilen entfernt werden. Benchmarking ist daher immer eine gute Idee, wenn Sie echte Leistungsprobleme haben. Als spontane, anekdotische Vermutung würde ich sagen, dass ein paar Tausend normalerweise "klein" sind.
jpmc26
9
Mit über 30 Jahren Erfahrung auf Plattformen wie Oracle, Teradata und MSSQL finde ich den Optimierer von PostgreSQL 10 nicht besonders intelligent. Selbst mit aktuellen Statistiken werden weniger effiziente Ausführungspläne generiert als in eine spezielle Richtung gezwungen. Die Bereitstellung struktureller Hinweise zur Kompensation dieser Probleme würde eine Lösung bieten, damit PostgreSQL in mehr Marktsegmenten wachsen kann. MEINER BESCHEIDENEN MEINUNG NACH.
Guido Leenders
75

Wahrscheinlich der einzig gültige Grund für die Verwendung

set enable_seqscan=false

Dies ist der Fall, wenn Sie Abfragen schreiben und schnell sehen möchten, wie der Abfrageplan tatsächlich aussehen würde, wenn große Datenmengen in den Tabellen enthalten wären. Oder natürlich, wenn Sie schnell bestätigen müssen, dass Ihre Abfrage keinen Index verwendet, nur weil der Datensatz zu klein ist.

Niraj Bhawnani
quelle
41
Diese kurze Antwort gibt tatsächlich einen guten Hinweis für Testzwecke
Dwery
3
Niemand beantwortet die Frage!
Ivailo Bardarov
@IvailoBardarov Der Grund, warum all diese anderen Vorschläge hier sind, ist, dass PostgreSQL diese Funktion nicht hat. Dies war eine bewusste Entscheidung der Entwickler, basierend auf der typischen Verwendung und den damit verbundenen langfristigen Problemen.
jpmc26
Ein netter Trick zum Testen: Ausführen set enable_seqscan=false, Ausführen Ihrer Abfrage und dann schnell ausführen set enable_seqscan=true, um postgresql wieder in das richtige Verhalten zu versetzen (und dies natürlich nicht in der Produktion, sondern nur in der Entwicklung!)
Brian Hellekin
2
@ BrianHellekin Besser, SET SESSION enable_seqscan=falsenur sich selbst zu beeinflussen
Izkata
19

Manchmal trifft PostgreSQL nicht die beste Auswahl an Indizes für eine bestimmte Bedingung. Angenommen, es gibt eine Transaktionstabelle mit mehreren Millionen Zeilen, von denen es für einen bestimmten Tag mehrere Hundert gibt, und die Tabelle enthält vier Indizes: transaction_id, client_id, date und description. Sie möchten die folgende Abfrage ausführen:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQL verwendet möglicherweise den Index transaction_description_idx anstelle von transaction_date_idx. Dies kann dazu führen, dass die Abfrage mehrere Minuten statt weniger als einer Sekunde dauert. Wenn dies der Fall ist, können Sie die Verwendung des Index am Datum erzwingen, indem Sie die Bedingung wie folgt verfälschen:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id
Ziggy Crueltyfree Zeitgeister
quelle
3
Gute Idee. Wenn wir jedoch die aktuelle Indexverwendung mit dieser Methode deaktivieren, greift der Postgresql-Abfrageoptimierer auf den nächsten geeigneten Index zurück. Daher kann keine Garantie dafür bestehen, dass das Optimierungsprogramm ausgewählt wird your_wanted_index. Es kann daher sein, dass die postgresql-Engine stattdessen nur einen Sequenz- / Primärschlüssel-Scan durchführt. Schlussfolgerung - Es gibt keine 100% zuverlässige Methode, um eine Indexverwendung für den PostgreSql-Server zu erzwingen.
Agnius Vasiliauskas
Was ist, wenn es keine whereBedingung außer zwei Tabellen oder verknüpften gibt und Postgres den Index nicht übernimmt ?
Luna Lovegood
@Surya das oben Gesagte gilt sowohl für WHERE als auch für JOIN ... ON-Bedingungen
Ziggy Crueltyfree Zeitgeister
18

Kurze Antwort

Dieses Problem tritt normalerweise auf, wenn die geschätzten Kosten eines Index-Scans zu hoch sind und die Realität nicht korrekt widerspiegeln. Möglicherweise müssen Sie den random_page_costKonfigurationsparameter verringern, um dies zu beheben. Aus der Postgres-Dokumentation :

Wenn Sie diesen Wert [...] reduzieren, bevorzugt das System Index-Scans. Durch Erhöhen werden Index-Scans relativ teuer.

Sie können überprüfen, ob ein niedrigerer Wert tatsächlich dazu führt, dass Postgres den Index verwendet (dies wird jedoch nur zum Testen verwendet ):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

Sie können den Standardwert mit SET random_page_cost = DEFAULT;wieder herstellen.

Hintergrund

Index-Scans erfordern nicht sequentielle Abrufe von Festplattenseiten. Postgres verwendet random_page_cost, um die Kosten solcher nicht sequentiellen Abrufe im Verhältnis zu sequentiellen Abrufen zu schätzen. Der Standardwert ist 4.0, wobei ein durchschnittlicher Kostenfaktor von 4 im Vergleich zu sequentiellen Abrufen angenommen wird (unter Berücksichtigung von Caching-Effekten).

Das Problem ist jedoch, dass dieser Standardwert in den folgenden wichtigen realen Szenarien ungeeignet ist:

1) Solid-State-Laufwerke

Wie die Dokumentation zugibt:

Speicher mit geringen zufälligen Lesekosten im Vergleich zu sequentiellen Laufwerken, z. B. Solid-State-Laufwerken, können möglicherweise besser mit einem niedrigeren Wert für modelliert werden random_page_cost.

Laut dem letzten Punkt dieser Folie aus einem Vortrag auf der PostgresConf 2018 random_page_costsollte auf etwas zwischen 1.0und 2.0für Solid-State-Laufwerke eingestellt werden.

2) Zwischengespeicherte Daten

Wenn die erforderlichen Indexdaten bereits im RAM zwischengespeichert sind, ist ein Index-Scan immer erheblich schneller als ein sequentieller Scan. Die Dokumentation sagt:

Entsprechend random_page_costkann eine [...] Verringerung angemessen sein , wenn sich Ihre Daten wahrscheinlich vollständig im Cache befinden.

Das Problem ist, dass Sie natürlich nicht leicht wissen können, ob die relevanten Daten bereits zwischengespeichert sind. Wenn jedoch häufig ein bestimmter Index abgefragt wird und das System über ausreichend RAM verfügt, werden die Daten wahrscheinlich zwischengespeichert und random_page_costsollten auf einen niedrigeren Wert gesetzt werden. Sie müssen mit verschiedenen Werten experimentieren und sehen, was für Sie funktioniert.

Möglicherweise möchten Sie auch die Erweiterung pg_prewarm für das explizite Zwischenspeichern von Daten verwenden.


emkey08
quelle
2
Ich musste sogar random_page_cost = 0.1 setzen, damit der Index-Scan in Pg 10.1 unter Ubuntu auf großen (~ 600M Zeilen Tabelle) funktioniert. Ohne die Optimierung dauerte der Seq-Scan (obwohl er parallel war) 12 Minuten (Beachten Sie, dass die Analysetabelle durchgeführt wurde!). Laufwerk ist SSD. Nach der Optimierung betrug die Ausführungszeit 1 Sekunde.
Anatoly Alekseev
Du hast meinen Tag gerettet. Ich wurde verrückt, als ich versuchte herauszufinden, wie genau dieselbe Abfrage in derselben Datenbank auf einem Computer 30 Sekunden und auf einem anderen weniger als 1 Sekunde dauerte, selbst nachdem ich analyse an beiden Enden ausgeführt hatte ... Wen es betrifft: den Befehl ' ALTER SYSTEM SET random_page_cost = x 'setzt den neuen Standardwert global.
Julien
10

Die Frage an sich ist sehr ungültig. Das Erzwingen (zum Beispiel durch enable_seqscan = off) ist eine sehr schlechte Idee. Es kann nützlich sein zu überprüfen, ob es schneller sein wird, aber Produktionscode sollte niemals solche Tricks verwenden.

Erklären Sie stattdessen die Analyse Ihrer Abfrage, lesen Sie sie und finden Sie heraus, warum PostgreSQL (Ihrer Meinung nach) einen schlechten Plan wählt.

Es gibt Tools im Web, die beim Lesen helfen, die Analyse zu erklären - eine davon ist EXPLAIN.depesz.com - von mir geschrieben.

Eine andere Möglichkeit besteht darin, sich dem # postgresql-Kanal im freenode irc-Netzwerk anzuschließen und mit den dortigen Mitarbeitern zu sprechen, um Ihnen zu helfen. Bei der Optimierung der Abfrage geht es nicht darum, "eine Frage zu stellen, eine Antwort zu erhalten, glücklich zu sein". Es ist eher ein Gespräch, bei dem viele Dinge überprüft und viele Dinge gelernt werden müssen.

user80168
quelle
2

Es gibt einen Trick, um Postgres zu verschieben, um einen Seqscan zu bevorzugen, der ein OFFSET 0in der Unterabfrage hinzufügt

Dies ist praktisch, um Anforderungen zu optimieren, die große / große Tabellen verknüpfen, wenn Sie nur die n ersten / letzten Elemente benötigen.

Nehmen wir an, Sie suchen nach den ersten / letzten 20 Elementen mit mehreren Tabellen mit 100.000 (oder mehr) Einträgen. Es macht keinen Sinn, die gesamte Abfrage über alle Daten hinweg aufzubauen / zu verknüpfen, wenn das, wonach Sie suchen, in den ersten 100 oder 1000 liegt Einträge. In diesem Szenario ist es beispielsweise mehr als zehnmal schneller, einen sequentiellen Scan durchzuführen.

Siehe Wie kann ich verhindern, dass Postgres eine Unterabfrage einfügt?

Antony Gibbs
quelle
Guter Trick. Obwohl ein guter Optimierer natürlich den Offset 0 weg optimieren sollte :-)
Guido Leenders