Aufgrund meiner Tätigkeit muss ich mich mit Situationen auseinandersetzen, in denen ein Entwickler eine Anfrage schreibt und denkt: „Die Basis ist smart, sie regelt alles selbst!«
In einigen Fällen (teils aus Unkenntnis der Fähigkeiten der Datenbank, teils aufgrund vorzeitiger Optimierungen) führt dieser Ansatz zum Auftreten von „Frankensteins“.
Zunächst gebe ich ein Beispiel für eine solche Anfrage:
-- для каждой ключевой пары находим ассоциированные значения полей
WITH RECURSIVE cte_bind AS (
SELECT DISTINCT ON (key_a, key_b)
key_a a
, key_b b
, fld1 bind_fld1
, fld2 bind_fld2
FROM
tbl
)
-- находим min/max значений для каждого первого ключа
, cte_max AS (
SELECT
a
, max(bind_fld1) bind_fld1
, min(bind_fld2) bind_fld2
FROM
cte_bind
GROUP BY
a
)
-- связываем по первому ключу ключевые пары и min/max-значения
, cte_a_bind AS (
SELECT
cte_bind.a
, cte_bind.b
, cte_max.bind_fld1
, cte_max.bind_fld2
FROM
cte_bind
INNER JOIN
cte_max
ON cte_max.a = cte_bind.a
)
SELECT * FROM cte_a_bind;
Um die Qualität einer Anfrage inhaltlich zu bewerten, erstellen wir einen beliebigen Datensatz:
CREATE TABLE tbl AS
SELECT
(random() * 1000)::integer key_a
, (random() * 1000)::integer key_b
, (random() * 10000)::integer fld1
, (random() * 10000)::integer fld2
FROM
generate_series(1, 10000);
CREATE INDEX ON tbl(key_a, key_b);
Es stellt sich heraus, dass Das Auslesen der Daten dauerte weniger als ein Viertel der Zeit Abfrageausführung:
Stück für Stück auseinander nehmen
Schauen wir uns die Anfrage genauer an und staunen:
- Warum steht hier WITH RECURSIVE, wenn es keine rekursiven CTEs gibt?
- Warum Min/Max-Werte in einem separaten CTE gruppieren, wenn sie dann sowieso an die Originalstichprobe gebunden sind?
+25 % Zeit - Warum am Ende ein unbedingtes „SELECT * FROM“ verwenden, um den vorherigen CTE zu wiederholen?
+14 % Zeit
In diesem Fall hatten wir großes Glück, dass Hash Join für die Verbindung ausgewählt wurde und nicht Nested Loop, denn dann hätten wir nicht nur einen CTE-Scan-Durchlauf, sondern 10K erhalten!
ein wenig über CTE ScanHier müssen wir uns daran erinnern Der CTE-Scan ähnelt dem Seq-Scan - also keine Indizierung, sondern nur eine vollständige Suche, die erforderlich wäre 10K x 0.3ms = 3000ms für Zyklen von cte_max oder 1K x 1.5ms = 1500ms beim Durchlaufen von cte_bind!
Was wollten Sie eigentlich als Ergebnis erreichen? Ja, normalerweise ist das die Frage, die irgendwo in der fünften Minute der Analyse von „dreistöckigen“ Abfragen auftaucht.
Wir wollten für jedes eindeutige Schlüsselpaar eine Ausgabe erstellen Min/Max aus der Gruppe nach key_a.
Also nutzen wir es dafür
SELECT DISTINCT ON(key_a, key_b)
key_a a
, key_b b
, max(fld1) OVER(w) bind_fld1
, min(fld2) OVER(w) bind_fld2
FROM
tbl
WINDOW
w AS (PARTITION BY key_a);
Da das Auslesen der Daten bei beiden Optionen etwa gleich etwa 4–5 ms dauert, bedeutet das für uns einen Zeitgewinn -32% - das ist in seiner reinsten Form Last von der Basis-CPU entfernt, wenn eine solche Anfrage oft genug ausgeführt wird.
Im Allgemeinen sollten Sie die Basis nicht dazu zwingen, „das Runde zu tragen, das Quadrat zu rollen“.
Source: habr.com