â vĂ€ga vĂ”imas ja mugav mehhanism, kui seotud andmete kallal teostatakse samu toiminguid âsĂŒvitsiâ. Kuid kontrollimata rekursioon on kuritegu, mis vĂ”ib viia kas lĂ”putu tĂ€itmiseni protsessi vĂ”i (mis juhtub sagedamini) âmĂ€lu ammendumiseniâ.

Andmebaasid töötavad selles osas samade printsiipide jĂ€rgi â "kui öeldi kaevama, kaevan mina". Teie pĂ€ring ei pruugi mitte ainult pidurdada naaberkeskkondi, pidevalt ressursse kasutades, vaid vĂ”ib ka âkukutadaâ kogu andmebaasi, âsöödesâ kogu saadaoleva mĂ€lu. SeetĂ”ttu igavese rekursiooni kaitse on arendaja kohustus.
PostgreSQL-is on rekursiivsete pĂ€ringute vĂ”imalus kasutades ilmnenud juba ammu versioonis 8.4, kuid siiani vĂ”ib regulaarselt kohata potentsiaalselt haavatavaid âalastiâ pĂ€ringuid. Kuidas end sellistest probleemidest vabaks saada?
Ărge kirjutage rekursiivseid pĂ€ringuid
Vaid kirjutage mitte-rekursiivseid. Lugupidamisega, Teie K.O.
Tegelikult pakub PostgreSQL piisavalt suurt funktsionaalsust, millega saab kasutada ei rekursiooni.
Kasutage tÀiesti erinevat lÀhenemist probleemile
MĂ”nikord vĂ”ib lihtsalt vaadata ĂŒlesannet âteise nurga altâ. NĂ€iteks tĂ”in sellise olukorra vĂ€lja artiklis â numberkogumi korrutamine ilma kasutaja defineeritud agregeerimisfunktsioone rakendamata:
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 -- tulemuse selektsioon
i DESC
LIMIT 1;Sellise pÀringu vÔib asendada matemaatika tundjate variandiga:
WITH src AS (
SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
exp(sum(ln(prime)))::integer val
FROM
src;Kasutage generate_series-i tsĂŒklite asemel
Eeldame, et meil on ĂŒlesanne genereerida kĂ”ik vĂ”imalikult eelnevad strings sĂ”ne 'abcdefgh':
WITH RECURSIVE T AS (
SELECT 'abcdefgh' str
UNION ALL
SELECT
substr(str, 1, length(str) - 1)
FROM
T
WHERE
length(str) > 1
)
TABLE T; Kas rekursioon on tÔesti vajalik?.. Kui kasutada LATERAL ja generate_seriesit, siis isegi CTE ei ole vajalik:
SELECT
substr(str, 1, ln) str
FROM
(VALUES('abcdefgh')) T(str)
, LATERAL(
SELECT generate_series(length(str), 1, -1) ln
) X;Muuda andmebaasi struktuuri
NÀiteks, teil on foorumi postituste tabel, kus on seosed, kes kellele vastas vÔi teema :
LOOD TABLE message(
message_id
uuid
PEAMI KEEPA
, reply_to
uuid
VIIDAT message
, body
text
);
LOODA INDICES message(reply_to); 
TĂŒĂŒpiline pĂ€ring, mis laadib kĂ”ik sĂ”numid ĂŒhe teema kohta, nĂ€eb vĂ€lja umbes nii:
WITH RECURSIVE T AS (
VALI
*
FROM
message
WHERE
message_id = $1
UNION ALL
VALI
m.*
FROM
T
JOIN
message m
ON m.reply_to = T.message_id
)
TABLE T;Kuna meil on alati vaja kogu teemat juursÔnumist, siis miks mitte lisada selle identifikaator igasse kirjesse automaatselt?
-- lisame teema ĂŒhise identifikaatori vĂ€lja ja indeksi sellele
ALTER TABLE message
ADD COLUMN theme_id uuid;
CREATE INDEX ON message(theme_id);
-- initsialiseerime teema identifikaatori trikkides sisestamisel
CREATE OR REPLACE FUNCTION ins() RETURNS TRIGGER AS $$
BEGIN
NEW.theme_id = CASE
WHEN NEW.reply_to IS NULL THEN NEW.message_id -- vĂ”tame algse sĂŒndmuse seest
ELSE ( -- vÔi message, millele vastame
VALI
theme_id
FROM
message
WHERE
message_id = NEW.reply_to
)
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER ins BEFORE INSERT
ON message
FOR EACH ROW
EXECUTE PROCEDURE ins(); 
NĂŒĂŒd saab kogu meie rekursiivpĂ€ringu vĂ€hendada lihtsalt selliseks:
SELECT
*
FROM
message
WHERE
theme_id = $1;Kasutage rakenduslikke "piirajaid"
Kui me ei saa mingil pÔhjusel andmebaasi struktuuri muuta, vaatame, millele toetuda, et isegi vea olemasolu andmetes ei tooks kaasa lÔpmatut rekursiooni.
Rekursiooni "sĂŒgavuse" loendur
Lihtsalt suurendame loendurit ĂŒhe vĂ”rra igal rekursiooni sammul kuni hetkeni, mil jĂ”uame piirini, mida peame ilmselgelt ebasobivaks:
WITH RECURSIVE T AS (
VALI
0 i
...
UNION ALL
VALI
i + 1
...
WHERE
T.i < 64 -- piir
) Pro: TsĂŒklite katsetamisel ei tee me ikkagi rohkem kui mÀÀratud iteratsioonide piiri "sĂŒgavusse".
Contra: Pole garantiid, et me ei töötle uuesti sama kirjet â nĂ€iteks sĂŒgavuses 15 ja 25, ning edasi iga +10 kohta. Ja keegi ei ole lubanud midagi "laiuses".
Formaalsete tingimuste kohaselt ei ole selline rekursioon lÔpmatu, kuid kui iga sammu korral arvude hulk suureneb eksponentsiaalselt, siis teame kÔik, kuidas see lÔpeb...
Teekonna "hoidja"
Kirjutame jÀrjestikku kÔik kohtunud identifikaatorid rekursiooni teel massiivi, mis on ainulaadne "tee" selle juurde:
WITH RECURSIVE T AS (
VALI
ARRAY[id] tee
...
UNION ALL
VALI
tee || id
...
WHERE
id ALL(T.tee) -- ei vasta ĂŒhele neist
) Pro: Andmete tsĂŒklite puhul ei töötle me kunagi sama kirje uuesti ĂŒhe ja sama tee jooksul.
Contra: Kuid me saame kÔiki kirjeid lÀbida, ilma et me korduksime.
Teepikkuse piirang
Kuna vĂ€ltida olukorda, kus rekurss sukeldub segase sĂŒgavuseni, saame kombineerida kaks eelmise meetodit. Kui me ei soovi toetada liigseid vĂ€lju, saame lisada rekurssimise jĂ€tkamise tingimusse teepikkuse hindamise:
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
) Valige sobiv meetod vastavalt oma maitsele!
Allikas: habr.com
