Viele, die bereits unseren Visualisierungsdienst fĂŒr PostgreSQL-PlĂ€ne nutzen, wissen möglicherweise nicht, dass er eine super FĂ€higkeit hat â schwer lesbare Teile der Server-Logs umzuwandeln⊠⊠in schön formatierte Abfragen mit kontextuellen Hinweisen zu den entsprechenden Knoten des Plans:

In dieser AusfĂŒhrung des zweiten Teils meines

Berichts auf der PGConf.Russia 2020 Den Transkript der ersten Teil, der sich den typischen Leistungsproblemen von Abfragen und ihren Lösungen widmet, finden Sie in dem Artikel
âRezepte fĂŒr krĂ€nkelnde SQL-Abfragenâ .

Uns erschien, dass diese unformatierte âTextwĂŒsteâ, die aus dem Log extrahierte Abfrage, Ă€uĂerst unschön aussieht und daher â unpraktisch ist.
Besonders, wenn Entwickler im Code den Abfragekörper âklebenâ (das ist zwar ein Anti-Muster, passiert aber trotzdem) in einer Zeile. Grauenhaft!

Lassen Sie uns das irgendwie schöner gestalten.
Wenn wir es schaffen, dies schön zu zeichnen, das heiĂt, den Abfragekörper zu zerlegen und wieder zusammenzusetzen, dann können wir auch an jedes Objekt dieser Abfrage einen Hinweis âanhĂ€ngenâ â was an dem entsprechenden Punkt des Plans geschah.

Der syntaktische Baum der Abfrage
Um dies zu erreichen, muss die Abfrage zuerst zerlegt werden.
Da unser

Systemkern auf NodeJS basiert auf GitHub finden können. Wir fĂŒttern den Abfragekörper in unsere Funktion â als Ausgabe erhalten wir den zerlegten syntaktischen Baum in Form eines JSON-Objekts.
Jetzt kann man ĂŒber diesen Baum in umgekehrter Richtung gehen und die Abfrage mit den gewĂŒnschten EinrĂŒckungen, FĂ€rbungen, Formatierungen, die wir wollen, wieder zusammenstellen. Nein, das ist nicht konfigurierbar, aber es erschien uns so am bequemsten.

Zugehörigkeit der Knoten von Abfrage und Plan

Jetzt sehen wir uns an, wie wir den Plan, den wir im ersten Schritt zerlegt haben, mit der Abfrage, die wir im zweiten Schritt zerlegt haben, kombinieren können.
Jetzt schauen wir, wie wir den Plan, den wir im ersten Schritt analysiert haben, und die Abfrage, die wir im zweiten Schritt zerlegt haben, kombinieren können.
Lassen Sie uns ein einfaches Beispiel nehmen â wir haben eine Abfrage, die CTE erstellt und zweimal daraus liest. Sie generiert so einen Plan.

CTE
Wenn man ihn sich genau anschaut, sieht man, dass bis zur Version 12 (oder beginnend mit ihr mit dem SchlĂŒsselwort MATERIALIZED) die Erstellung .

Das heiĂt, wenn wir irgendwo in der Abfrage eine CTE-Generierung sehen und irgendwo im Plan einen Knoten CTE, dann stehen diese Knoten eindeutig miteinander in Beziehung, und wir können sie sofort zusammenfassen.
Eine âAufgabe mit Sternchenâ: CTEs können geschachtelt sein.

Es gibt sehr ungeschickt geschachtelte und sogar gleichnamige. Zum Beispiel können Sie innerhalb von CTE A eine CTE X, und auf der gleichen Ebene innerhalb von CTE B wieder eine CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...Bei der Zuordnung mĂŒssen Sie dies verstehen. Es ist sehr schwierig, dies âmit den Augenâ zu begreifen â selbst den Plan zu sehen, selbst den Anfragekörper zu sehen â ist sehr schwer. Wenn Sie eine komplexe, geschachtelte CTE-Generierung haben, groĂe Abfragen â dann ist es sogar völlig unbewusst.
UNION
Wenn in unserer Abfrage das SchlĂŒsselwort UNION [ALL] (der Operator zum VerknĂŒpfen zweier Abfragen) vorhanden ist, dann entspricht ihm im Plan entweder ein Knoten AnfĂŒgen, oder irgendein Rekursive Vereinigung.

Das, was âdarĂŒberâ ist, ist der erste Nachkomme unseres Knotens, was âdarunterâ ist â der zweite. Wenn ĂŒber UNION wir mehrere Blöcke gleichzeitig âverkettetâ haben, dann UNION - der Knoten wird trotzdem nur einer sein, aber er hat nicht zwei Kinder, sondern viele â in der Reihenfolge, wie sie erscheinen: AnfĂŒgen(...) -- #1 UNION ALL (...) -- #2 UNION ALL (...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3: innerhalb der Generierung einer rekursiven Abfrage (
Eine âAufgabe mit Sternchenâ) kann es ebenfalls mehr als einen geben.WITH RECURSIVEAber rekursiv ist immer nur der letzte Block nach dem letzten UNION. Alles, was darĂŒber ist â ist eine, aber eine andere. UNIONWITH RECURSIVE T AS( (...) -- #1 UNION ALL (...) -- #2, hier endet die Generierung des Startzustandes der Rekursion UNION ALL (...) -- #3, nur dieser Block ist rekursiv und kann auf T verweisen ) ... UNION:
Solche Beispiele muss man ebenfalls âentwirrenâ können. In diesem Beispiel sehen wir, dass -Abschnitte in unserer Abfrage 3 StĂŒck waren. Dementsprechend entspricht einem UNION-Knoten, und einem anderen â UNION entspricht AnfĂŒgen-Knoten, wĂ€hrend der andere â Rekursive Vereinigung.

Daten lesen und schreiben.
Alles, aufgeschlĂŒsselt, jetzt wissen wir, welches StĂŒck der Anfrage welchem StĂŒck des Plans entspricht. Und in diesen StĂŒcken können wir leicht und unkompliziert die Objekte finden, die âgelesenâ werden.
Aus der Sicht der Anfrage wissen wir nicht â ob es eine Tabelle oder eine CTE ist, aber sie werden durch denselben Knoten bezeichnet. Bereichsvariable. Und im Hinblick auf âlesbarâ ist dies ebenfalls eine recht begrenzte Menge an Knoten:
Seq Scan auf [tbl]Bitmap-Hub-Scan auf [tbl]Index [Nur] Scan [RĂŒckwĂ€rts] unter Verwendung von [idx] auf [tbl]CTE-Scan auf [cte]EinfĂŒgen/Update/Delete auf [tbl]
Wir wissen um die Struktur des Plans und der Anfrage, wir kennen die Ăbereinstimmung der Blöcke, die Namen der Objekte â wir machen eine eindeutige Zuordnung.

Again, die Aufgabe âmit Sternâ. Wir nehmen die Anfrage, fĂŒhren sie aus, wir haben keine Aliase â wir haben einfach zweimal aus einer CTE gelesen.

Wir schauen in den Plan â was ist los? Warum tauchte das Alias auf? Wir haben nicht danach gefragt. Woher kommt es mit âNummerâ?
PostgreSQL fĂŒgt es selbst hinzu. Man muss einfach verstehen, dass genau dieses Alias fĂŒr unsere Zwecke der Zuordnung zum Plan keinerlei Bedeutung hat, es wurde einfach hier hinzugefĂŒgt. Lassen wir es unbeachtet.
Zweiter die Aufgabe âmit Sternâ: Wenn wir aus einer partitionierten Tabelle lesen, erhalten wir einen Knoten AnfĂŒgen oder ZusammenfĂŒhren AnhĂ€ngen, der aus einer groĂen Anzahl von âKindernâ bestehen wird, und jedes von ihnen wird eine Art Scanaus der Tabellen-Partition sein: Seq Scan, Bitmap-Hub-Scan oder Index Scan. Aber in jedem Fall werden diese âKinderâ keine komplexen Anfragen sein â so kann man diese Knoten von AnfĂŒgen unterscheiden UNION.

Diese Knoten verstehen wir ebenfalls, bĂŒndeln sie âzu einem Haufenâ und sagen: "alles, was du aus der megatable gelesen hast, ist hier und nach unten im Baum".
âEinfacheâ Knoten zur Datenerfassung

Werte scannen entsprechen VALUES in der Anfrage.
Ergebnis â dies ist eine Anfrage ohne FROM wie SELECT 1. Oder wenn Sie ein absichtlich falsches Ausdruck in WHERE-Block haben (dann taucht das Attribut One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- oder 0 = 1Result (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
One-Time Filter: false
Funktion Scan âmappenâ zu den gleichnamigen SRF.
Mit Unteranfragen wird es komplizierter â leider verwandeln sie sich nicht immer in InitPlan/SubPlan. Manchmal verwandeln sie sich in ... Join oder ... Anti Join, besonders wenn Sie etwas schreiben wie WHERE NOT EXISTS .... Und dort ist es nicht immer möglich zu kombinieren â im Plantext gibt es fĂŒr die entsprechenden Knoten der Operatoren keine Operatoren.
Again, die Aufgabe âmit Sternâ: mehrere VALUES in der Anfrage. In diesem Fall erhalten Sie auch im Plan mehrere Knoten Werte scannen.

Sie können sie durch ânummerierteâ Suffixe unterscheiden â sie werden genau in der Reihenfolge der entsprechenden VALUES-Blöcke in der Anfrage von oben nach unten hinzugefĂŒgt.
Datenverarbeitung
Es scheint, als hĂ€tten wir alles in unserer Anfrage durchgearbeitet â nur noch bleibt Begrenzung.

Aber hier ist alles einfach â solche Knoten wie Begrenzung, Sortieren, Aggregat, FensterAggregat, Eindeutig âmappenâ eins zu eins auf die entsprechenden Operatoren in der Anfrage, wenn sie dort vorhanden sind. Hier gibt es keine âSternchenâ und keine Schwierigkeiten.

JOIN
Schwierigkeiten treten auf, wenn wir versuchen zu kombinieren JOIN untereinander. Das ist nicht immer möglich, aber es kann.

Aus der Sicht des Anfragen-Parsers haben wir einen Knoten JoinExpr, der genau zwei Nachkommen hat â links und rechts. Dies ist entsprechend das, was âĂŒberâ Ihrem JOIN steht und das, was âunterâ ihm im Antrag steht.
Und aus der Perspektive des Plans sind das zwei Nachkommen eines * Schleife/* Beitreten-Knotens. Verschachtelte Schleife, Hash Anti Join,⊠â so etwas in der Art.
Lassen Sie uns einfache Logik anwenden: Wenn wir Tabellen A und B haben, die im Plan âjoinenâ, dann konnten sie im Antrag entweder so angeordnet sein A-JOIN-B, oder B-JOIN-A. Lassen Sie uns versuchen, diese zu kombinieren, versuchen wir es umgekehrt, und so weiter, bis solche Paare enden.
Nehmen wir unseren Syntaxbaum, nehmen wir unseren Plan, schauen wir sie uns an⊠sieht nicht aus wie!

Wir zeichnen es in Form von Graphen neu â oh, jetzt sieht es schon nach etwas aus!

Lassen Sie uns darauf achten, dass wir Knoten haben, die gleichzeitig Kinder B und C haben â es ist uns egal, in welcher Reihenfolge. Wir kombinieren sie und drehen das Bild des Knotens um.

Schauen wir uns noch einmal an. Jetzt haben wir Knoten mit Kindern A und Paaren (B + C) â wir kombinieren auch diese.

Ausgezeichnet! Es stellt sich heraus, dass wir diese beiden JOIN aus dem Antrag mit den Knoten des Plans erfolgreich kombiniert haben.
Leider ist diese Aufgabe nicht immer lösbar.

Wenn im Antrag beispielsweise A JOIN B JOIN C, aber im Plan zuerst die âĂ€uĂerenâ Knoten A und C verbunden wurden. Und im Antrag gibt es keinen solchen Operator, wir haben nichts hervorzuheben, an das wir den Hinweis anhĂ€ngen können. Dasselbe gilt fĂŒr das âKommaâ, wenn Sie schreiben A, B.
Aber in den meisten FĂ€llen gelingt es, fast alle Knoten âzu lösenâ und eine solche Profilierung links nach der Zeit zu erhalten â buchstĂ€blich wie in Google Chrome, wenn Sie Code in JavaScript analysieren. Sie sehen, wie viel Zeit jede Zeile und jeder Operator âausgefĂŒhrtâ wurden.

Und um es fĂŒr Sie einfacher zu machen, haben wir eine Speicherung eingerichtet , wo Sie Ihre PlĂ€ne zusammen mit den zugehörigen Anfragen speichern und spĂ€ter finden oder einen Link mit jemandem teilen können.
Wenn Sie jedoch einfach eine unleserliche Anfrage in eine angemessene Form bringen mĂŒssen, verwenden Sie .

Quelle: habr.com
