PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonist

Rekursioon — 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”.

PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonist
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 WITH RECURSIVE 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 „SQL HowTo: 1000 ja ĂŒks viis agregeerimiseks” — 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 sotsiaalmeedias:

LOOD TABLE message(
  message_id
    uuid
      PEAMI KEEPA
, reply_to
    uuid
      VIIDAT message
, body
    text
);
LOODA INDICES message(reply_to);

PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonist
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();

PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonist
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...

PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonistvt "KĂŒsimus terade kohta malelaual"

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.

PostgreSQL Antipatterns: „Igavik ei ole piir!”, ehk natuke rekursioonistvt "Ratsaniku liikumise probleem"

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

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster