FĂŒrchten Sie Operationen, die Buffers bringenâŠ
Anhand einer kleinen Abfrage betrachten wir einige universelle AnsĂ€tze zur Optimierung von Abfragen in PostgreSQL. Ob Sie sie nutzen oder nicht, liegt bei Ihnen, aber es ist gut, darĂŒber Bescheid zu wissen.
In den spĂ€teren Versionen von PG könnte sich die Situation mit der âIntelligenzâ des Planers Ă€ndern, aber fĂŒr 9.4/9.6 sieht es hier ungefĂ€hr gleich aus wie in den Beispielen hier.
Ich nehme eine völlig reale Abfrage:
SELECT
TRUE
FROM
"Dokument" d
INNER JOIN
"DokumentErweiterung" doc_ex
USING("@Dokument")
INNER JOIN
"DokumentTyp" t_doc ON
t_doc."@DokumentTyp" = d."DokumentTyp"
WHERE
(d."Person3" = 19091 or d."Mitarbeiter" = 19091) AND
d."$Entwurf" IS NULL AND
d."Gelöscht" IS NOT TRUE AND
doc_ex."Zustand"[1] IS TRUE AND
t_doc."DokumentTyp" = 'Arbeitsplan'
LIMIT 1; ĂŒber Tabellennamen und FelderZu den ârussischenâ Bezeichnungen von Feldern und Tabellen kann man unterschiedlich stehen, aber das ist Geschmackssache. Da keine auslĂ€ndischen Entwickler haben und PostgreSQL es uns erlaubt, Namen sogar mit Hieroglyphen zu geben, wenn sie in AnfĂŒhrungszeichen gesetzt sind, ziehen wir es vor, Objekte eindeutig und klar zu benennen, um MissverstĂ€ndnisse zu vermeiden.
Schauen wir uns den entstandenen Plan an:

144ms und fast 53K Buffers â das sind ĂŒber 400MB Daten! Und wir haben GlĂŒck, wenn alle diese Daten zum Zeitpunkt unserer Abfrage im Cache sind, sonst wird es beim Auslesen von der Festplatte um ein Vielfaches lĂ€nger.
Der Algorithmus ist das Wichtigste!
Um jede Abfrage irgendwie zu optimieren, muss man zunÀchst verstehen, was sie eigentlich tun soll.
Lassen Sie die Entwicklung der Datenbankstruktur fĂŒr diesen Artikel vorerst auĂen vor, und vereinbaren wir, dass wir relativ "kostengĂŒnstig" die Abfrage umschreiben und/oder einige benötigte Indizes auf die Datenbank anwenden können.
Also, die Abfrage:
â ĂŒberprĂŒft die Existenz eines beliebigen Dokuments
â im gewĂŒnschten Zustand und bestimmten Typ
â wo der Autor oder AusfĂŒhrende der benötigte Mitarbeiter ist
JOIN + LIMIT 1
HĂ€ufig ist es fĂŒr den Entwickler einfacher, eine Abfrage zu schreiben, bei der zunĂ€chst eine groĂe Anzahl von Tabellen verbunden wird und dann aus dieser Vielzahl nur ein einzelner Datensatz ĂŒbrig bleibt. Aber einfacher fĂŒr den Entwickler heiĂt nicht effektiver fĂŒr die Datenbank.
In unserem Fall gab es insgesamt 3 Tabellen â und welcher EffektâŠ
Lassen Sie uns zunÀchst die Verbindung zur Tabelle "DokumentTyp" entfernen und gleichzeitig der Datenbank mitteilen, dass wir einen einzigartigen Typ von Datensatz haben (das wissen wir, aber der Planer ahnt es noch nicht):
WITH T AS (
SELECT
"@DokumentTyp"
FROM
"DokumentTyp"
WHERE
"DokumentTyp" = 'Arbeitsplan'
LIMIT 1
)
...
WHERE
d."DokumentTyp" = (TABLE T)
...Ja, wenn die Tabelle/CTE aus einem einzigen Feld eines einzigen Datensatzes besteht, kann man in PG sogar so schreiben, anstatt
d."DokumentTyp" = (SELECT "@DokumentTyp" FROM T LIMIT 1)âFauleâ Berechnungen in PostgreSQL-Abfragen
BitmapOr vs UNION
In einigen FĂ€llen kann ein Bitmap Heap Scan sehr kostspielig sein - zum Beispiel in unserer Situation, in der eine recht groĂe Anzahl von DatensĂ€tzen in die erforderlichen Bedingungen fĂ€llt. Wir haben es erhalten aufgrund von OR-Bedingungen, die sich in BitmapOr-Operationen im Plan verwandelt haben.
Kehren wir zur ursprĂŒnglichen Aufgabe zurĂŒck - wir mĂŒssen den Datensatz finden, der einem der Bedingungen entspricht - das heiĂt, es gibt keinen Grund, alle 59K DatensĂ€tze nach beiden Bedingungen zu durchsuchen. Es gibt einen Weg, eine Bedingung abzuarbeiten und erst zur zweiten ĂŒberzugehen, wenn bei der ersten nichts gefunden wurde.Uns hilft eine solche Konstruktion:
(
SELECT
...
LIMIT 1
)
UNION ALL
(
SELECT
...
LIMIT 1
)
LIMIT 1Der âĂ€uĂereâ LIMIT 1 garantiert, dass die Suche endet, sobald der erste Datensatz gefunden wird. Und wenn dieser bereits im ersten Block gefunden wird, erfolgt die AusfĂŒhrung des zweiten nicht (never executed im Plan).
âVerstecken unter CASEâ komplexe Bedingungen
Im ursprĂŒnglichen Anfrage gibt es einen Ă€uĂerst ungĂŒnstigen Punkt - die ĂberprĂŒfung des Status der verknĂŒpften Tabelle âDokumentErweiterungâ. UnabhĂ€ngig von der Wahrhaftigkeit der anderen Bedingungen in der Aussage (zum Beispiel, d."Entfernt" IS NOT TRUE), wird diese Verbindung immer durchgefĂŒhrt und âkostet Ressourcenâ. Ob mehr oder weniger dafĂŒr aufgewendet wird, hĂ€ngt vom Umfang dieser Tabelle ab.
Aber man kann die Anfrage so modifizieren, dass die Suche nach dem verknĂŒpften Datensatz nur dann erfolgt, wenn es tatsĂ€chlich notwendig ist:
SELECT
...
FROM
"Dokument" d
WHERE
...
/*index cond*/ AND
CASE
WHEN "$Entwurf" IS NULL AND "Entfernt" IS NOT TRUE THEN (
SELECT
"Status"[1] IS TRUE
FROM
"DokumentErweiterung"
WHERE
"@Dokument" = d."@Dokument"
)
END Da aus der verknĂŒpften Tabelle keine Felder fĂŒr das Ergebnis benötigt werden, können wir den JOIN in eine Bedingung eines Subqueries umwandeln.Wir lassen die indizierten Felder âauĂerhalbâ des CASE, einfache Bedingungen in den WHEN-Block einbringen - und jetzt wird die âschwereâ Anfrage nur ausgefĂŒhrt, wenn wir in das THEN ĂŒbergehen.
Mein Nachname ist âInsgesamtâ
Wir aggregieren die endgĂŒltige Anfrage mit all den oben beschriebenen Mechaniken:
Wir erstellen die resultierende Abfrage unter BerĂŒcksichtigung aller oben beschriebenen Mechanismen:
MIT T AS (
SELECT
"@Dokumenttyp"
FROM
"Dokumenttyp"
WHERE
"Dokumenttyp" = 'Arbeitsplan'
)
(
SELECT
TRUE
FROM
"Dokument" d
WHERE
("Person3", "Dokumenttyp") = (19091, (TABEL T)) AND
FALLS
WENN "$Entwurf" NULL IST UND "Gelöscht" IST NICHT TRUE DANN (
SELECT
"Zustand"[1] IST TRUE
FROM
"Dokumenterweiterung"
WHERE
"@Dokument" = d."@Dokument"
)
END
LIMIT 1
)
UNION ALL
(
SELECT
TRUE
FROM
"Dokument" d
WHERE
("Dokumenttyp", "Mitarbeiter") = ((TABEL T), 19091) AND
FALLS
WENN "$Entwurf" NULL IST UND "Gelöscht" IST NICHT TRUE DANN (
SELECT
"Zustand"[1] IST TRUE
FROM
"Dokumenterweiterung"
WHERE
"@Dokument" = d."@Dokument"
)
END
LIMIT 1
)
LIMIT 1;Anpassung [an] Indizes
Ein geschultes Auge hat bemerkt, dass die indizierten Bedingungen in den UNION-Unterblocks leicht variieren â das liegt daran, dass wir bereits geeignete Indizes in der Tabelle haben. Wenn es sie nicht gegeben hĂ€tte, wĂ€re es sinnvoll gewesen, sie zu erstellen: Dokument(Person3, Dokumenttyp) und Dokument(Dokumenttyp, Mitarbeiter).
zur Reihenfolge der Felder in ROW-BedingungenAus der Sicht des Planers kann man natĂŒrlich auch schreiben (A, B) = (constA, constB), und (B, A) = (constB, constA). Aber bei der Aufzeichnung in der Reihenfolge der Felder im Index, ist eine solche Abfrage einfach leichter zu debuggen.
Was steht an?

Leider hatten wir Pech und im ersten UNION-Block wurde nichts gefunden, daher wurde der zweite tatsĂ€chlich ausgefĂŒhrt. Aber selbst dabei â nur 0,037 ms und 11 Buffers!
Wir haben die Abfrage beschleunigt und die "Bearbeitung" von Daten im Speicher um mehrere Tausend Mal, indem wir ziemlich einfache Techniken angewendet haben â ein gutes Ergebnis bei geringem Aufwand. đ
Quelle: habr.com
