â vĂ€ga vĂ”imas ja mugav mehhanism, kui seotud andmete kohta tehakse samu toiminguid "sĂŒgavale". Kuid kontrollimatu rekurssioon on paha, mis vĂ”ib viia kas lĂ”putu tĂ€itmiseni protsessi, vĂ”i (mis juhtub sagedamini) kuni kogu saadaval mĂ€luruumi "nĂ€ljatamiseni"..

Andmebaasid töötavad selles osas samade pĂ”himĂ”tete kohaselt â "kĂ€sid kaevata, mina ka kaevan". Teie pĂ€ring ei pruugi mitte ainult aeglustada naaberprotsesse, pidevalt kasutades protsessori ressursse, vaid vĂ”ib ka "maha kukkuda" kogu andmebaas, "söödud" kogu saadaval mĂ€lu. SeetĂ”ttu kaitse lĂ”putu rekursiooni eest on arendaja kohustus.
PostgreSQL-is tekkis vÔimalus kasutada rekursiivseid pÀringuid lÀbi juba ammuses versioonis 8.4, kuid siiani vÔib regulaarselt kohata potentsiaalselt haavatavaid "kaitsetuid" pÀringuid. Kuidas end sellistest probleemidest vabaks saada?
Ărge kirjutage rekursiivseid pĂ€ringuid
KĂŒll aga kirjutage rekursiivseid. Lugupidamisega, Teie K.O.
Tegelikult pakub PostgreSQL piisavalt palju funktsioone, millega saab ei rakendada rekursiooni.
Kasutage pĂ”himĂ”tteliselt teistsugust lĂ€henemist ĂŒlesandele
MĂ”nikord saab ĂŒlesandele lihtsalt vaadata 'teise nurga alt'. Sellise olukorra nĂ€idet tĂ”in ma artiklis â rea numbrite korrutamine ilma kasutaja mÀÀratud aggregeerimisfunktsioonide rakendamiseta:
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 -- lÔpp-tulemuse valik
i DESC
LIMIT 1;Sellist pÀringut saab asendada matemaatikagurude 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 asemel silmusid
Oletame, et meie ees on ĂŒlesanne genereerida kĂ”ik vĂ”imalikud prefiksid stringile '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 siinkohal on tÔepoolest vajalik rekursioon?.. Kui kasutada LATERAL ja generate_series, siis ei ole isegi CTE-d vajalikud:
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 sÔnumite tabel, kus on seosed, kes kellele vastas, vÔi teema :
CREATE TABLE message(
message_id
uuid
PRIMARY KEY
, reply_to
uuid
REFERENCES message
, body
text
);
CREATE INDEX ON message(reply_to); 
Ja tĂŒĂŒpiline pĂ€ring, mis laadib kĂ”iki sĂ”numeid ĂŒhe teema kaupa, nĂ€eb vĂ€lja umbes selline:
WITH RECURSIVE T AS (
SELECT
*
FROM
message
WHERE
message_id = $1
UNION ALL
SELECT
m.*
FROM
T
JOIN
message m
ON m.reply_to = T.message_id
)
TABLE T;Kuna meil on alati vaja kogu teemat juure sÔnumist, miks siis mitte lisada selle identifikaator igasse kirjesse automaatsete vahenditega?
-- lisame vĂ€lja ĂŒhise teema identifikaatoriga ja indeksi sellele
ALTER TABLE message
ADD COLUMN theme_id uuid;
CREATE INDEX ON message(theme_id);
-- initialize teema identifikaatori triggeris 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 algsest sĂŒndmusest
ELSE ( -- vÔi sÔnumist, millele vastame
SELECT
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 rekursiivne pĂ€ring kokku tĂ”mmata vaid selliseks:
SELECT
*
FROM
message
WHERE
theme_id = $1;Kasutada rakenduslikke "piire"
Kui me mingil pÔhjusel ei saa andmebaasi struktuuri muuta, vaatame, millele toetuda, et isegi andmetes oleva vea korral ei tekiks lÔpmatu rekurss.
Rekursiooni 'sĂŒgavuse' loendur
Lihtsalt suurendame loendurit ĂŒhe vĂ”rra igal rekurssioni sammul kuni hetkeni, mil saavutame piiri, mida peame tĂ€iesti ebapiisavaks:
WITH RECURSIVE T AS (
SELECT
0 i
...
UNION ALL
SELECT
i + 1
...
WHERE
T.i < 64 -- piir
) Pro: TsĂŒklilisuse katsetamisel teeme me ikkagi mitte rohkem kui mÀÀratud iteratsioonide 'sĂŒgavuse' piiri.
Contra: Ei ole garantiid, et me ei töötle uuesti sama kirjet â nĂ€iteks sĂŒgavusel 15 ja 25, ning edasi iga +10 sĂŒgavuse kohta. Ja 'laiuse' kohta ei ole keegi midagi lubanud.
Formaalsetel alustel ei saa see rekurss olema lÔpmatu, kuid kui igal sammul rekordite arv suurendab eksponentsiaalselt, teame kÔik, kuidas see lÔpeb...
Teekonna hoidja
Kirjutame jÀrjestikku kÔik kohtunud rekursiooni kÀigus objektide identifikaatorid massti, mis on ainulaadne 'tee' kuni sinna:
REKURSIOON T NAGU (
VALIGE
ARRAY[id] tee
...
ĂHENDAGE KĂIK
VALIGE
tee || id
...
KUS
id ALL(T.tee) -- ei ĂŒhti ĂŒhegi
) Pro: Kui andmetes on tsĂŒkkel, ei töötle me kindlasti sama kirjet ĂŒhe ja sama tee raames uuesti.
Contra: Kuid samas saame kÀia lÀbi, tÔeliselt, kÔik kirjed, nii et me ei korduks.
Teepikkuse piirang
Kuna vĂ€ltida olukorda, kus rekursioon rĂ€ndab arusaamatusse sĂŒgavusse, vĂ”ime kahe eelneva meetodi kombineerida. VĂ”i, kui me ei soovi liialdavaid vĂ€lju toetada, tĂ€iendada rekursiooni jĂ€tkamise tingimist teepikkuse hindamisega:
REKURSIOON T NAGU (
VALIGE
ARRAY[id] tee
...
ĂHENDAGE KĂIK
VALIGE
tee || id
...
KUS
id ALL(T.tee) JA
array_length(T.tee, 1) < 10
) Valige endale sobiv meetod!
Allikas: habr.com
