Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Die klassische Frage, die ein Entwickler an seinen DBA oder ein Geschäftsinhaber an einen PostgreSQL-Berater stellt, klingt fast immer gleich: „Warum dauert es so lange, bis Anfragen in der Datenbank abgeschlossen sind?“

Traditionelle Gründe:

  • ineffizienter Algorithmus
    wenn Sie sich entscheiden, mehreren CTEs über mehrere Zehntausend Datensätze beizutreten
  • irrelevante Statistiken
    wenn sich die tatsächliche Verteilung der Daten in der Tabelle bereits stark von der von ANALYZE beim letzten Mal erfassten unterscheidet
  • Ressourcen „einstecken“.
    und die dedizierte Rechenleistung der CPU reicht nicht mehr aus, es werden ständig Gigabyte an Speicher nachgepumpt oder die Festplatte kann nicht mit allen „Bedürfnissen“ der Datenbank mithalten
  • blockieren aus konkurrierenden Prozessen

Und wenn Blockaden ziemlich schwer zu erkennen und zu analysieren sind, dann brauchen wir sie für alles andere Abfrageplan, die mit erhalten werden kann EXPLAIN-Operator (Es ist natürlich besser, sofort zu ERKLÄREN (ANALYSE, PUFFER) ...) Oder auto_explain-Modul.

Aber wie in derselben Dokumentation angegeben,

„Einen Plan zu verstehen ist eine Kunst, und um ihn zu meistern, bedarf es einer gewissen Erfahrung …“

Mit dem richtigen Werkzeug können Sie aber auch darauf verzichten!

Wie sieht ein Abfrageplan normalerweise aus? So ähnlich:

Index Scan using pg_class_relname_nsp_index on pg_class (actual time=0.049..0.050 rows=1 loops=1)
  Index Cond: (relname = $1)
  Filter: (oid = $0)
  Buffers: shared hit=4
  InitPlan 1 (returns $0,$1)
    ->  Limit (actual time=0.019..0.020 rows=1 loops=1)
          Buffers: shared hit=1
          ->  Seq Scan on pg_class pg_class_1 (actual time=0.015..0.015 rows=1 loops=1)
                Filter: (relkind = 'r'::"char")
                Rows Removed by Filter: 5
                Buffers: shared hit=1

oder so:

"Append  (cost=868.60..878.95 rows=2 width=233) (actual time=0.024..0.144 rows=2 loops=1)"
"  Buffers: shared hit=3"
"  CTE cl"
"    ->  Seq Scan on pg_class  (cost=0.00..868.60 rows=9972 width=537) (actual time=0.016..0.042 rows=101 loops=1)"
"          Buffers: shared hit=3"
"  ->  Limit  (cost=0.00..0.10 rows=1 width=233) (actual time=0.023..0.024 rows=1 loops=1)"
"        Buffers: shared hit=1"
"        ->  CTE Scan on cl  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.021..0.021 rows=1 loops=1)"
"              Buffers: shared hit=1"
"  ->  Limit  (cost=10.00..10.10 rows=1 width=233) (actual time=0.117..0.118 rows=1 loops=1)"
"        Buffers: shared hit=2"
"        ->  CTE Scan on cl cl_1  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.001..0.104 rows=101 loops=1)"
"              Buffers: shared hit=2"
"Planning Time: 0.634 ms"
"Execution Time: 0.248 ms"

Aber den Plan im Text „vom Blatt“ zu lesen, ist sehr schwierig und unklar:

  • wird im Knoten angezeigt Summe nach Teilbaumressourcen
    Das heißt, um zu verstehen, wie viel Zeit es gedauert hat, einen bestimmten Knoten auszuführen, oder wie viel genau dieses Lesen aus der Tabelle Daten von der Festplatte hervorgebracht hat, müssen Sie irgendwie voneinander subtrahieren
  • Knotenzeit wird benötigt Mit Schleifen multiplizieren
    Ja, die Subtraktion ist nicht die komplexeste Operation, die „im Kopf“ ausgeführt werden muss. Schließlich wird die Ausführungszeit als Durchschnitt für eine Ausführung eines Knotens angegeben, und es können Hunderte davon sein
  • Nun, und das alles zusammen hindert uns daran, die Hauptfrage zu beantworten – also wer "der schwächste Link"?

Als wir versuchten, das alles mehreren Hundert unserer Entwickler zu erklären, stellten wir fest, dass es von außen in etwa so aussah:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Und das bedeutet, dass wir...

Werkzeug

Darin haben wir versucht, alle wichtigen Mechanismen zusammenzustellen, die dabei helfen, zu verstehen, „wer schuld ist und was zu tun ist“ entsprechend dem Plan und der Anfrage. Nun, und teilen Sie einen Teil Ihrer Erfahrungen mit der Community.
Treffen und nutzen - erklären.tensor.ru

Sichtbarkeit von Plänen

Ist der Plan leicht zu verstehen, wenn er so aussieht?

Seq Scan on pg_class (actual time=0.009..1.304 rows=6609 loops=1)
  Buffers: shared hit=263
Planning Time: 0.108 ms
Execution Time: 1.800 ms

Nicht sehr.

Aber so, in abgekürzter FormWenn man die Schlüsselindikatoren trennt, wird es viel klarer:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Aber wenn der Plan komplizierter ist, wird er zur Rettung kommen Zeitverteilung im Kreisdiagramm nach Knoten:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Nun, bei den schwierigsten Optionen hat er es eilig, zu helfen Fortschrittsdiagramm:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Beispielsweise gibt es durchaus nicht triviale Situationen, in denen ein Plan mehr als eine tatsächliche Wurzel haben kann:

Worüber EXPLAIN schweigt und wie man es zum Reden bringtWorüber EXPLAIN schweigt und wie man es zum Reden bringt

Strukturelle Hinweise

Nun, wenn die gesamte Struktur des Plans und seine wunden Stellen bereits festgelegt und sichtbar sind, warum nicht sie dem Entwickler hervorheben und in „russischer Sprache“ erklären?

Worüber EXPLAIN schweigt und wie man es zum Reden bringtWir haben bereits ein paar Dutzend solcher Empfehlungsvorlagen gesammelt.

Zeilenweiser Abfrageprofiler

Wenn Sie nun die ursprüngliche Abfrage mit dem analysierten Plan überlagern, können Sie sehen, wie viel Zeit für jede einzelne Anweisung aufgewendet wurde – etwa so:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

...oder sogar so:

Worüber EXPLAIN schweigt und wie man es zum Reden bringt

Ersetzen von Parametern in einer Anfrage

Wenn Sie dem Plan nicht nur eine Anfrage, sondern auch deren Parameter aus der DETAIL-Zeile des Protokolls „angehängt“ haben, können Sie diese zusätzlich in eine der Optionen kopieren:

  • mit Wertersetzung in der Abfrage
    zur direkten Ausführung auf Ihrer Basis und zur weiteren Profilierung

    SELECT 'const', 'param'::text;
  • mit Wertersetzung über PREPARE/EXECUTE
    um die Arbeit des Schedulers zu emulieren, wenn der parametrische Teil ignoriert werden kann – zum Beispiel bei der Arbeit an partitionierten Tabellen

    DEALLOCATE ALL;
    PREPARE q(text) AS SELECT 'const', $1::text;
    EXECUTE q('param'::text);
    

Archiv der Pläne

Einfügen, analysieren, mit Kollegen teilen! Die Pläne bleiben archiviert und Sie können später darauf zurückgreifen: EXPLAIN.tensor.ru/archive

Wenn Sie jedoch nicht möchten, dass andere Ihren Plan sehen, vergessen Sie nicht, das Kontrollkästchen „Nicht im Archiv veröffentlichen“ zu aktivieren.

In den folgenden Artikeln werde ich über die Schwierigkeiten und Entscheidungen sprechen, die bei der Analyse eines Plans auftreten.

Source: habr.com

Kommentar hinzufügen