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

Hinweis auf PostgreSQL

Der Flammenkrieg dieser Woche auf der pgsql-Performance-Liste dreht sich wieder einmal um die Tatsache, dass PostgreSQL nicht über die traditionelle Hint-Syntax verfügt, die in anderen Datenbanken verfügbar ist. Dafür gibt es eine Mischung aus technischen und pragmatischen Gründen:

  • Das Einführen von Hinweisen ist eine häufige Quelle späterer Probleme, da das einmalige Korrigieren einer Abfragestelle in einem speziellen Fall kein sehr robuster Ansatz ist. Wenn Ihr Datensatz wächst und sich möglicherweise auch die Verteilung ändert, kann die Idee, die Sie angedeutet haben, als sie noch klein war, zu einer immer schlechteren Idee werden.
  • Das Hinzufügen einer nützlichen Hint-Schnittstelle würde den Optimierungscode verkomplizieren, der so wie er ist schwierig genug zu pflegen ist. Ein Grund dafür, dass PostgreSQL beim Ausführen von Abfragen so gut funktioniert, liegt unter anderem daran, dass Wohlfühlcode („Hinweise können wir auf unserer Anbietervergleichs-Funktionsliste abhaken!“), der sich in Bezug auf die Erstellung der Datenbank nicht wirklich bezahlt macht besser genug ist, um seine fortgesetzte Wartung zu rechtfertigen, wird von der Politik abgelehnt. Wenn es nicht funktioniert, wird es nicht hinzugefügt. Und bei objektiver Bewertung sind Hinweise im Durchschnitt eher ein Problem als eine Lösung.
  • Die Art von Problemen, bei denen Hinweise funktionieren, können Optimierungsfehler sein. Die PostgreSQL-Community reagiert schneller als jeder andere in der Branche auf echte Fehler im Optimierer. Fragen Sie herum, und Sie müssen nicht viele PostgreSQL-Benutzer treffen, bevor Sie einen finden, der einen Fehler gemeldet und beobachtet hat, wie er am nächsten Tag behoben wurde.

Nun, die wichtigste, absolut gültige Antwort auf das Herausfinden, dass Hinweise fehlen, normalerweise von DBAs, die daran gewöhnt sind, lautet:„Nun, wie gehe ich mit einem Optimierungsfehler um, wenn ich darauf stoße?“ Wie bei allen technischen Arbeiten heutzutage besteht normalerweise ein enormer Druck, die schnellstmögliche Lösung zu finden, wenn ein fehlerhaftes Abfrageproblem auftaucht.
Wenn PostgreSQL keine Möglichkeiten hätte, mit dieser Situation umzugehen, gäbe es keine ernsthaften PostgreSQL-Datenbanken für die Produktion . Der Unterschied besteht darin, dass die Dinge, die Sie in dieser Datenbank anpassen, mehr darauf beruhen, die Entscheidungen zu beeinflussen, die der Optimierer bereits auf ziemlich subtile Weise trifft, als dass Sie ihm nur sagen, was er tun soll. Dies sind Hinweise im wahrsten Sinne des Wortes, sie haben nur nicht die Benutzeroberfläche, um darauf hinzuweisen, dass Benutzer anderer Datenbanken, die neu bei PostgreSQL sind, suchen.
In diesem Sinne, werfen wir einen Blick darauf, was Sie können in PostgreSQL tun, um schlechte Abfragepläne und Optimierungsfehler zu umgehen, insbesondere die Dinge, von denen viele Leute zu glauben scheinen, dass sie nur mit Hinweisen gelöst werden können:

  • join_collapse_limit:  Dies passt an, wie viel Flexibilität der Optimierer hat, um Joins mehrerer Tabellen neu anzuordnen. Normalerweise versucht es jede mögliche Kombination, wenn Joins neu angeordnet werden können (was meistens der Fall ist, es sei denn, Sie verwenden einen Outer Join). Das Verringern von join_collapse_limit, vielleicht sogar auf 1, entfernt einige oder alle dieser Flexibilität. Wenn es auf 1 gesetzt ist, erhalten Sie die Joins in der Reihenfolge, in der Sie sie geschrieben haben, Punkt. Das Planen einer großen Anzahl von Joins ist eine der schwierigsten Aufgaben für den Optimierer. Jeder Join vergrößert Fehler in Schätzungen und erhöht die Planungszeit für Abfragen. Wenn die zugrunde liegende Art Ihrer Daten offensichtlich macht, welche Reihenfolgenverknüpfungen stattfinden sollten, und Sie nicht davon ausgehen, dass sich das jemals ändern wird, können Sie sie mit diesem Parameter sperren, sobald Sie die richtige Reihenfolge gefunden haben.
  • random_page_cost:Dieser Parameter ist standardmäßig 4,0 und legt fest, wie teuer die Suche nach einer zufälligen Seite auf der Festplatte relativ zu einem Referenzwert von 1,0 ist. Wenn Sie nun das Verhältnis von zufälligen zu sequenziellen E/A auf normalen Festplatten messen, werden Sie feststellen, dass diese Zahl näher bei 50 liegt. Warum also 4.0? Erstens, weil es in Community-Tests besser geklappt hat als größere Werte. Zweitens werden in vielen Fällen insbesondere Indexdaten im Speicher zwischengespeichert, wodurch die effektiven Kosten zum Lesen dieser Werte geringer werden. Wenn Ihr Index beispielsweise zu 90 % im RAM zwischengespeichert ist, bedeutet dies, dass Sie in 10 % der Fälle die 50-mal so teure Operation ausführen müssen; dadurch würden Ihre effektiven random_page_cost etwa 5 betragen.  Diese Art von realer Situation ist der Grund, warum die Standardeinstellung sinnvoll ist, wo sie ist. Ich sehe normalerweise, dass beliebte Indizes> 95 % Cache im Arbeitsspeicher erhalten. Wenn es wahrscheinlicher ist, dass sich Ihr Index vollständig im RAM befindet, kann die Reduzierung von random_page_cost auf knapp über 1,0 eine vernünftige Wahl sein, um zu zeigen, dass es nicht teurer ist als jeder andere Lesevorgang. Gleichzeitig können zufällige Suchvorgänge auf stark ausgelasteten Systemen viel teurer sein, als Sie erwarten würden, wenn Sie sich nur Einzelbenutzer-Simulationen ansehen. Ich musste random_page_cost auf 60 setzen, damit die Datenbank keine Indizes mehr verwendet, wenn der Planer falsch eingeschätzt hat, wie teuer sie sein würden. Typischerweise entsteht diese Situation durch einen Sensitivitätsschätzungsfehler seitens des Planers – wenn Sie mehr als etwa 20 % einer Tabelle scannen, weiß der Planer, dass die Verwendung eines sequentiellen Scans viel effizienter ist als ein Index-Scan. Die hässliche Situation, in der ich dieses Verhalten viel früher erzwingen musste, trat auf, als der Planer erwartete, dass 1 % der Zeilen zurückgegeben werden, es aber tatsächlich eher bei 15 % lag.
  • work_mem:  Passt an, wie viel Speicher für Abfragen verfügbar ist, die Sortieren, Hashing und ähnliche speicherbasierte Operationen durchführen. Dies ist nur eine grobe Richtlinie für Abfragen, keine feste Grenze, und ein einzelner Client kann am Ende ein Vielfaches von work_mem verwenden, wenn er eine Abfrage ausführt. Dementsprechend müssen Sie darauf achten, diesen Wert in der Datei postgresql.conf nicht zu hoch einzustellen. Was Sie stattdessen tun können, ist es, es vor dem Ausführen einer Abfrage zu setzen, die wirklich davon profitiert, zusätzlichen Speicher zum Speichern von Sortier- oder Hash-Daten zu haben. Sie können diese Abfragen manchmal finden, wenn Sie langsame Abfragen mit log_min_duration_statement protokollieren. Sie können sie auch finden, indem Sie log_temp_files aktivieren, die jedes Mal protokollieren, wenn work_mem zu klein ist, und daher Sortiervorgänge auf die Festplatte übertragen werden, anstatt im Speicher zu erfolgen.
  • OFFSET 0:  PostgreSQL ordnet Unterabfragen in Form eines Joins neu an, sodass es dann die reguläre Join-Reihenfolgelogik verwenden kann, um es zu optimieren. In einigen Fällen kann diese Entscheidung wirklich schlecht sein, da die Art von Dingen, die Leute dazu neigen, als Unterabfragen zu schreiben, aus irgendeinem Grund etwas schwieriger einzuschätzen sind (ich sage das aufgrund der Anzahl solcher problematischer Abfragen, die ich sehe). Ein raffinierter Trick, den Sie anwenden können, um diese Logik zu verhindern, besteht darin, OFFSET 0 an das Ende der Unterabfrage zu stellen. Dies ändert nichts an den Ergebnissen, aber das Einfügen des Typs des Limit-Abfrageknotens, der zum Ausführen von OFFSET verwendet wird, verhindert eine Neuanordnung. Die Unterabfrage wird dann immer so ausgeführt, wie es die meisten Leute erwarten – als eigener isolierter Abfrageknoten.
  • enable_seqscan, enable_indexscan, enable_bitmapscan:Das Deaktivieren einer dieser Funktionen zum Nachschlagen von Zeilen in einer Tabelle ist ein ziemlich großer Hammer, um dringend zu empfehlen, diese Art von Scan zu vermeiden (nicht immer zu verhindern – wenn es keine Möglichkeit gibt, Ihren Plan auszuführen, aber ein seqscan, erhalten Sie einen seqscan, selbst wenn die Parameter ausgeschaltet sind). Ich empfehle diese hauptsächlich, um Abfragen nicht zu beheben, sondern um mit EXPLAIN zu experimentieren und zu sehen, warum die andere Art von Scan bevorzugt wurde.
  • enable_nestloop, enable_hashjoin, enable_mergejoin:  Wenn Sie vermuten, dass Ihr Problem in der Art des verwendeten Joins liegt und nicht darin, wie die Tabellen gelesen werden, versuchen Sie, den Typ, den Sie in Ihrem Plan sehen, mit einem dieser Parameter zu deaktivieren, und führen Sie dann EXPLAIN aus wieder. Fehler in Sensitivitätsschätzungen können leicht dazu führen, dass ein Join mehr oder weniger effizient erscheint, als er wirklich ist. Und wiederum kann es sehr aufschlussreich sein, zu sehen, wie sich der Plan ändert, wenn die aktuelle Join-Methode deaktiviert ist, warum er sich überhaupt für diese entschieden hat.
  • enable_hashagg, enable_material:  Diese Funktionen sind relativ neu für PostgreSQL. Die aggressive Verwendung von Hash-Aggregation wurde in Version 8.4 eingeführt, und eine aggressivere Materialisierung in 9.0. Wenn Sie diese Arten von Knoten in Ihrer EXPLAIN
    Ausgabe sehen und sie scheinen etwas falsch zu machen, weil dieser Code so viel neuer ist, ist es etwas wahrscheinlicher, dass er eine Einschränkung oder einen Fehler hat als einige der älteren Funktionen. Wenn Sie einen Plan hatten, der in älteren Versionen von PostgreSQL gut funktionierte, aber einen dieser Knotentypen verwendet und daher eine viel schlechtere Leistung zu erbringen scheint, kann das Deaktivieren dieser Funktionen Sie manchmal zu dem früheren Verhalten zurückbringen – und etwas Licht ins Dunkel bringen warum der Optimierer das Falsche getan hat, als nützliches Feedback. Beachten Sie, dass dies im Allgemeinen die Art und Weise ist, wie fortgeschrittenere Funktionen in PostgreSQL eingeführt werden:  mit der Option, sie zu Fehlerbehebungszwecken zu deaktivieren, wenn sich herausstellt, dass es eine Planregression im Vergleich dazu gibt, wie frühere Versionen Dinge ausgeführt haben.
  • cursor_tuple_fraction:Wenn Sie nicht beabsichtigen, alle Zeilen aus einer Abfrage zurückzulesen, sollten Sie einen Cursor verwenden, um dies zu implementieren. In diesem Fall versucht der Optimierer zu priorisieren, ob er Ihnen schnell die erste Zeile zurückgibt oder ob er basierend auf diesem Parameter lieber die gesamte Abfrage optimiert. Standardmäßig geht die Datenbank davon aus, dass Sie 10 % der Abfrage erneut zurücklesen, wenn Sie einen Cursor verwenden. Wenn Sie diesen Parameter anpassen, können Sie ihn dahingehend beeinflussen, dass Sie erwarten, dass Sie weniger oder mehr als das lesen.

Alle diese Parameter und Abfrageoptimierungen sollten als Triage-Anpassungen betrachtet werden. Sie möchten nicht für immer mit diesen ausgeführt werden (außer vielleicht für join_collapse_limit). Sie verwenden sie, um aus einem Stau herauszukommen, und dann werden Sie hoffentlich herausfinden, was die wirkliche zugrunde liegende Ursache des schlechten Plans ist – schlechte Statistiken, Optimierungsbeschränkung/-fehler oder etwas anderes – und dann das Problem aus dieser Richtung angehen. Je mehr Sie das Verhalten des Optimierers in eine Richtung treiben, desto stärker sind Sie zukünftigen Änderungen in Ihren Daten ausgesetzt, wodurch dieser Push nicht mehr korrekt ist. Wenn Sie sie richtig verwenden, um zu untersuchen, warum Sie den falschen Plan erhalten haben (der Ansatz, den ich im Kapitel zur Abfrageoptimierung von PostgreSQL 9.0 High Performance verwendet habe), sollte die Art und Weise, wie Sie auf Dinge in PostgreSQL hinweisen, dazu führen, dass Sie jeden Lauf verlassen. bei schlechtem Verhalten des Optimierers ein wenig schlauer zu sein, wie diese Klasse von Problemen in Zukunft vermieden werden kann