â ein Ă€uĂerst leistungsfĂ€higer und praktischer Mechanismus, wenn die gleichen Aktionen bei miteinander verbundenen Daten ânach untenâ ausgefĂŒhrt werden. Aber unkontrollierte Rekursion ist eine Gefahr, die zu endlosen AusfĂŒhrungen des Prozesses fĂŒhren kann oder (was hĂ€ufiger vorkommt) zu dem âVerschlingenâ des gesamten verfĂŒgbaren Speichers..

Datenbanksysteme arbeiten in dieser Hinsicht nach denselben Prinzipien â âSie sagten, graben Sie, also grabe ich.â. Ihre Abfrage kann nicht nur benachbarte Prozesse verlangsamen, indem sie stĂ€ndig CPU-Ressourcen verbraucht, sondern auch die gesamte Datenbank âzum Absturz bringenâ, indem sie den gesamten verfĂŒgbaren Speicher âauffrisstâ. Daher ist der Schutz vor endloser Rekursion die Verantwortung des Entwicklers selbst.
In PostgreSQL besteht die Möglichkeit, rekursive Abfragen ĂŒber schon seit den âguten alten Zeitenâ der Version 8.4, aber bis heute können regelmĂ€Ăig potenziell anfĂ€llige âschutzloseâ Abfragen festgestellt werden. Wie können Sie sich von solchen Problemen befreien?
Schreiben Sie keine rekursiven Abfragen.
Sondern schreiben Sie nicht-rekursiven. Mit Respekt, Ihr K.O.
TatsÀchlich bietet PostgreSQL eine Vielzahl an Funktionen, die man nutzen kann, um nicht Rekursion anzuwenden.
Einen grundlegend anderen Ansatz fĂŒr das Problem verwenden
Manchmal kann man die Aufgabe auch einfach aus einer anderen Perspektive betrachten. Ein Beispiel fĂŒr eine solche Situation habe ich in dem Artikel gebracht â die Multiplikation einer Anzahl von Zahlen ohne benutzerdefinierte Aggregatfunktionen:
WITH RECURSIVE src AS (
SELECT '{2,3,5,7,11,13,17,19}'::integer[] arr
)
, T(i, val) AS (
SELECT
1::bigint
, 1
UNION ALL
SELECT
i + 1
, val * arr[i]
FROM
T
, src
WHERE
i <= array_length(arr, 1)
)
SELECT
val
FROM
T
ORDER BY -- Auswahl des finalen Ergebnisses
i DESC
LIMIT 1;Eine solche Abfrage kann durch die Methode von Mathematikern ersetzt werden:
WITH src AS (
SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
exp(sum(ln(prime)))::integer val
FROM
src;generate_series anstelle von Schleifen verwenden
Angenommen, wir stehen vor der Aufgabe, alle möglichen PrĂ€fixe fĂŒr die Zeichenfolge 'abcdefgh':
zu generieren:
WITH RECURSIVE T AS (
SELECT 'abcdefgh' str
UNION ALL
SELECT
substr(str, 1, length(str) - 1)
FROM
T
WHERE
length(str) > 1
)
TABLE T; Ist hier wirklich Rekursion nötig?.. Wenn wir LATERAL und generate_series, verwenden, dann sind nicht einmal CTE erforderlich:
WĂHLEN
substr(str, 1, ln) str
VON
(WERT('abcdefgh')) T(str)
, LATERAL(
WĂHLEN generate_series(lĂ€nge(str), 1, -1) ln
) X;Datenbankstruktur Àndern
Zum Beispiel haben Sie eine Tabelle mit ForenbeitrÀgen, die zeigt, wer wem geantwortet hat oder den Thread in :
ERSTELLEN TABELLE nachricht(
nachricht_id
uuid
PRIMARY KEY
, antwort_auf
uuid
REFERENZEN nachricht
, text
text
);
INDEX ERSTELLEN FĂR nachricht(antwort_auf); 
Ein typischer Abfrage zum Laden aller Nachrichten zu einem Thema sieht etwa so aus:
MIT REKURSIV T ALS (
WĂHLEN
*
VON
nachricht
WO
nachricht_id = $1
UNION ALLE
WĂHLEN
m.*
VON
T
JOIN
nachricht m
ON m.antwort_auf = T.nachricht_id
)
TABELLE T;Aber da wir immer das gesamte Thema vom Ausgangsnachricht benötigen, warum fĂŒgen wir nicht seine ID automatisch in jeden Datensatz ?
-- FĂŒgen wir ein Feld mit der gemeinsamen Themen-ID und einen Index darauf hinzu
ALTER TABLE nachricht
FĂGE SPALTE theme_id uuid HINZU;
INDEX ERSTELLEN FĂR nachricht(theme_id);
-- initialisieren Sie die Themen-ID in einem Trigger bei der EinfĂŒgung
CREATE OR REPLACE FUNCTION ins() RETURNS TRIGGER AS $$
BEGIN
NEW.theme_id = FALLS
WENN NEW.antwort_auf IST NULL DANN NEW.nachricht_id -- nehmen Sie vom Startereignis
SONST ( -- oder von der Nachricht, auf die wir antworten
WĂHLEN
theme_id
VON
nachricht
WO
nachricht_id = NEW.antwort_auf
)
END;
GEBEN SIE NEU ZURĂCK;
END;
$$ SPRACHE plpgsql;
CREATE TRIGGER ins VOR INSERT
AUF nachricht
FĂR JEDEN DATENZEILE
FĂHREN SIE DAS VERFAHREN ins AUS; 
Jetzt kann unsere rekursive Abfrage auf folgendes reduziert werden:
SELECT
*
FROM
message
WHERE
theme_id = $1;Anwendungsbezogene "Grenzen" nutzen
Falls wir die Struktur der Datenbank aus bestimmten GrĂŒnden nicht Ă€ndern können, sollten wir ĂŒberlegen, auf welche Aspekte wir uns stĂŒtzen können, damit selbst ein Fehler in den Daten nicht zu einer endlosen Rekursion fĂŒhrt.
ZĂ€hler fĂŒr die Rekursionstiefe
Wir erhöhen einfach den ZÀhler bei jedem Schritt der Rekursion bis zu dem Punkt, den wir als eindeutig unangemessen erachten:
WITH RECURSIVE T AS (
SELECT
0 i
...
UNION ALL
SELECT
i + 1
...
WHERE
T.i < 64 -- Grenze
) Pro: Bei einem Versuch zur Endlosschleife werden wir dennoch nicht mehr als die angegebene Anzahl an Iterationen "nach unten" durchfĂŒhren.
Contra: Es gibt keine Garantie, dass wir nicht einen gleichen Datensatz mehrfach verarbeiten â beispielsweise auf der Tiefe 15 und 25, und dann jeweils alle 10 tiefer. Auch zum "seitlichen" Zugriff wurde nichts versprochen.
Formal wird diese Rekursion nicht unendlich sein, aber wenn die Anzahl der DatensĂ€tze bei jedem Schritt exponentiell zunimmt, wissen wir alle, wie das endetâŠ
WĂ€chter des "Pfades"
Wir fĂŒgen nacheinander alle Identifikatoren der Objekte, die wir auf dem Weg der Rekursion treffen, in ein Array ein, das den einzigartigen "Weg" zu ihnen darstellt:
WITH RECURSIVE T AS (
SELECT
ARRAY[id] path
...
UNION ALL
SELECT
path || id
...
WHERE
id ALL(T.path) -- entspricht keinem der
) Pro: Wenn ein Zyklus in den Daten vorhanden ist, werden wir auf keinen Fall den gleichen Datensatz im Rahmen eines einzigen Weges erneut verarbeiten.
Contra: Aber dabei können wir tatsÀchlich alle DatensÀtze durchlaufen, ohne uns zu wiederholen.
EinschrÀnkung der PfadlÀnge
Um die Situation des "Umherirrens" der Rekursion in unverstĂ€ndlicher Tiefe zu vermeiden, können wir die beiden vorherigen Methoden kombinieren. Oder, falls wir keine zusĂ€tzlichen Felder unterstĂŒtzen wollen, die Fortsetzung der Rekursion mit einer Bewertung der PfadlĂ€nge ergĂ€nzen:
WITH RECURSIVE T AS (
SELECT
ARRAY[id] path
...
UNION ALL
SELECT
path || id
...
WHERE
id ALL(T.path) AND
array_length(T.path, 1) < 10
) WĂ€hlen Sie die Methode nach Ihrem Geschmack!
Quelle: habr.com
