PostgreSQL antipatternid: andmehulkade ja valikute saatmine SQL-is

Arendaja peab perioodiliselt edastama pĂ€ringus teatud parameetrid vĂ”i isegi terve valiku «sisendisse». MĂ”nikord on selle ĂŒlesande lahendused vĂ€ga kummalised. Alustame „vastupidiselt” ja vaatame, kuidas mitte teha, miks ja kuidas saab paremini.
PostgreSQL antipatternid: andmehulkade ja valikute saatmine SQL-is
Otsene „sĂŒstitud” vÀÀrtuste lisamine pĂ€ringu kehasse

Tavaliselt nÀeb see vÀlja umbes nii:

query = "SELECT * FROM tbl WHERE id = " + value


 vÔi nii:

query = "SELECT * FROM tbl WHERE id = :param".format(param=value)

Sellest meetodist on rÀÀgitud, kirjutatud ja

isegi joonistatud piisavalt: Peaaegu alati viib see -

PostgreSQL antipatternid: andmehulkade ja valikute saatmine SQL-is

otse SQL sĂŒstimise teele ja liigse koormuseni Ă€ri loogikas, mis peab „liimima” teie pĂ€ringu rea. Osaliselt on see lĂ€henemine Ă”igustatud ainult juhul, kui on vaja

kasutada jaotust PostgreSQL 10 ja vanemates versioonides efektiivse plaani saamiseks. Nendes versioonides mÀÀratakse skaneeritavate sektsioonide loetelu enne edastatavaid parameetreid, ainult pÀringu keha alusel. $n-argumendid

kohtade hoidjad

Kasutamine parameetrid - see on hea, sest see vĂ”imaldab kasutada PREPARED STATEMENTS , vĂ€hendades koormust nii Ă€riloogikas (pĂ€ringurea moodustamine ja edastamine toimub ainult ĂŒks kord) kui ka andmebaasi serveris (ei ole vaja korduvat kĂ€sitlemist ja planeerimist iga pĂ€ringu eksemplari jaoks).Muutuva arvu argumente

Probleemid ootavad meid, kui soovime edastada ette teadmata arvu argumente:

... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...

Kui jĂ€tta pĂ€ring selliseks, siis see hoiab meid kĂŒll potentsiaalsete sĂŒstide eest, kuid toob endaga kaasa vajaduse pĂ€ringu liimimiseks/analĂŒĂŒsimiseks

iga variandi jaoks argumendi arvust. See on juba parem kui teha seda iga kord, kuid selleta on vĂ”imalik hakkama saada.Piisab, kui edastada vaid ĂŒks parameeter, mis sisaldab

sĂŒserialiseeritud massiivi esitlemist ... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}':

Ainus erinevus on vajadus selgelt muundada argument soovitud massiivi tĂŒĂŒbiks. Kuid see ei tekita probleeme, kuna me juba teame, kuhu suuname.

Valiku (maatriksi) edastamine

Tavaliselt on need erinevad variandid andmekogumite edastamiseks andmebaasi â€žĂŒhe pĂ€ringuga”:

INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...

Lisaks eespool kirjeldatud probleemidele „liimimisega” vĂ”ib see meid veelgi viia

mĂ€lu ĂŒletamiseni out of memory ja serveri langemisele. PĂ”hjus on lihtne — PG reservib tĂ€iendavat mĂ€lu, ning ridade arv komplektis on piiratud ainult rakenduse Ă€riloogika soovidega. Erakordsetel juhtudel on nĂ€htud «numbrilised» argumendid ĂŒle $9000 — Ă€ra nii tee.

Korrigeerime pÀringu, rakendades juba «kaheastmelist» serialiseerimist:

INSERT INTO tbl
SELECT
  unnest[1]::text k
, unnest[2]::integer v
FROM (
  SELECT
    unnest($1::text[])::text[] -- $1 : '{"{a,1}","{b,2}","{c,3}","{d,4}"}'
) T;

Jah, juhul kui massiivi sees on «keerukamad» vÀÀrtused, tuleb need ĂŒmber ringi panna jutumĂ€rkidesse.
On selge, et sellisel viisil saab «lahti vÔetakse» valiku, millel on meelevaldne arv vÀlju.

unnest, unnest, 


Perioodiliselt esinevad variandid, kus «massivÀli» asemel edastatakse mitu «veergude massiivi», millest ma varem rÀÀkisin eelmisel artiklis:

SELECT
  unnest($1::text[]) k
, unnest($2::integer[]) v;

Sellise meetodi puhul, kui teete vigu erinevate veergude vÀÀrtuste loomiseks, on vĂ€ga lihtne saada tĂ€iesti ĂŒllatavaid tulemusi, mis sĂ”ltuvad ka serveri versioonist:

-- $1 : '{a,b,c}', $2 : '{1,2}'
-- PostgreSQL 9.4
k | v
-----
a | 1
b | 2
c | 1
a | 2
b | 1
c | 2
-- PostgreSQL 11
k | v
-----
a | 1
b | 2
c |

JSON

Alates versioonist 9.3 on PostgreSQL-is ilmunud tĂ€isfunktsionaalsed funktsioonid JSON-tĂŒĂŒbi kĂ€sitlemiseks. SeetĂ”ttu, kui teie sisendi parameetrite mÀÀramine toimub brauseris, saate seal otse luua JSON-objekt SQL pĂ€ringu jaoks:

SELECT
  key k
, value v
FROM
  json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'

Eelnevate versioonide jaoks saab sama meetodit kasutada each(hstore), kuid korrektne «kokkuvÔte» keerukate objektide majandamisega hstore'is vÔib tekitada probleeme.

json_populate_recordset

Kui teate eelnevalt, et andmed «sisenemise» JSON-massiivist lĂ€hevad mĂ”ne tabeli tĂ€itmiseks, saate oluliselt kokku hoida «de-referentsite» vĂ€ljade mÀÀramisel ja sobivate tĂŒĂŒpide konverteerimisel, kasutades funktsiooni json_populate_recordset:

SELECT
  *
FROM
  json_populate_recordset(
    NULL::pg_class
  , $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
  );

json_to_recordset

See funktsioon lihtsalt «lahti vÔetakse» edastatud objekti massiiv valikuks, tuginedes tabeli formaadile:

SELECT
  *
FROM
  json_to_recordset($1::json) T(k text, v integer);
-- $1 : '[{"k":"a","v":1},{"k":"b","v":2}]'
k | v
-----
a | 1
b | 2

AJUTINE TABEL

Aga kui edastatava valiku andmemaht on vĂ€ga suur, siis selle laskmine ĂŒhte serialiseeritud parameetrisse — on keeruline, ja mĂ”nikord isegi vĂ”imatu, kuna see nĂ”uab ĂŒhekordset suurte mĂ€lu mahtude eraldamistNĂ€iteks, kui teil on vaja kaua-kaua koguda suur andmepakett vĂ€lisest sĂŒsteemist ja siis tahate selle korraga andmebaasis töödelda.

Sellisel juhul on parim lahendus ajutised tabelid:

CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- korrata palju-palju kordi
...
-- siin teeme midagi kasulikku terve selle tabeliga

Meetod on hea just harvade suurte andmemahtude edastamiseks andmeid.
Andmestruktuuri kirjeldamise seisukohalt erineb ajutine tabel „tavalisest” ainuselt ĂŒhe tunnuse poolest sĂŒsteemitabelis pg_classtoob kaasa selle, et "saadetamise" hetkel server ei ole veel kĂ€ivitatud ja kĂ”ik lĂ€heb katki. pg_type, pg_depend, pg_attribute, pg_attrdef, 
 — muidu ei erinegi.

SeetĂ”ttu genereerib veebisĂŒsteemides, kus on palju lĂŒhiajalisi ĂŒhendusi, iga kord nende jaoks selline tabel uusi sĂŒsteemi registreeringuid, mis kustutatakse andmebaasiga ĂŒhenduse lĂ”petamisel. Tulemuseks on, kontrollimatu TEMP TABLE kasutamine viib pg_catalog'i tabelite „paisumiseni” ja paljude nende kasutavate operatsioonide aeglustumiseni.
Loomulikult on selle vastu vĂ”imalik vĂ”idelda aeg-ajalt VACUUM FULL'i lĂ€biviimisega sĂŒsteemi katalooge tabeleid.

Sessiooni muutujad

Oletame, et varasema juhtumi andmete töötlemine on piisavalt keeruline ĂŒhe SQL-kĂ€sku jaoks, kuid tahame seda teha piisavalt sageli. See tĂ€hendab, et soovime kasutada protseduurilist töötlemist DO-plokis, kuid andmete edastamine ajutiste tabelite kaudu on liiga kulukas.

$n-parameetrite kasutamine anonĂŒĂŒmse ploki andmete edastamiseks ei ole samuti vĂ”imalik. Lahenduseks on meil sessiooni muutujad ja funktsioon current_setting.

Enne versiooni 9.2 oli vajalik eelnevalt konfigureerida eriline nimede ruum custom_variable_classes oma sessiooni muutujate jaoks. KÀsitles ajakohastes versioonides saab kirjutada umbkaudu jÀrgmiselt:

SET my.val = '{1,2,3}';
DO $$
DECLARE
  id integer;
BEGIN
  FOR id IN (SELECT unnest(current_setting('my.val')::integer[])) LOOP
    RAISE NOTICE 'id : %', id;
  END LOOP;
END;
$$ LANGUAGE plpgsql;
-- NOTICE: id : 1
-- NOTICE: id : 2
-- NOTICE: id : 3

Teistes toetatud protseduurilistes keeltes vÔib leida ka muid lahendusi.

Kas teate veel viise? Jagage oma mÔtteid kommentaarides!

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