PostgreSQL antipatterns: kova su „mirusiųjų“ miniomis

PostgreSQL vidinių mechanizmų ypatumai leidžia kai kuriose situacijose būti labai greitam, o kitose – „nelabai greitai“. Šiandien mes sutelksime dėmesį į klasikinį konflikto tarp to, kaip veikia DBVS ir ką kūrėjas daro su ja, pavyzdį. UPDATE vs MVCC principai.

Trumpa istorija iš puikus straipsnis:

Kai eilutė pakeičiama komanda UPDATE, iš tikrųjų atliekamos dvi operacijos: DELETE ir INSERT. IN dabartinė eilutės versija xmax yra lygus operacijos, kuri atliko UPDATE, skaičiui. Tada jis sukuriamas nauja versija ta pati eilutė; jo xmin reikšmė sutampa su ankstesnės versijos xmax reikšme.

Praėjus tam tikram laikui po šios operacijos atlikimo, senoji arba nauja versija, priklausomai nuo COMMIT/ROOLBACK, bus pripažintas „miręs“ (negyvos eilutės) kai praeina VACUUM pagal lentelę ir išvalyta.

PostgreSQL antipatterns: kova su „mirusiųjų“ miniomis

Bet tai neįvyks iš karto, tačiau problemų su „mirusiaisiais“ galima įgyti labai greitai - pakartotinai arba masinis įrašų atnaujinimas dideliame stale, o kiek vėliau susidursite su ta pačia situacija VACUUM nepadės.

# 1: Man patinka tai perkelti

Tarkime, kad jūsų metodas veikia su verslo logika, ir staiga jis supranta, kad reikėtų atnaujinti X lauką kokiame nors įraše:

UPDATE tbl SET X = <newX> WHERE pk = $1;

Tada, vykdymo eigoje, paaiškėja, kad Y laukas taip pat turėtų būti atnaujintas:

UPDATE tbl SET Y = <newY> WHERE pk = $1;

... o tada dar ir Z – kam gaišti laiką smulkmenoms?

UPDATE tbl SET Z = <newZ> WHERE pk = $1;

Kiek šio įrašo versijų dabar turime duomenų bazėje? Taip, 4 vienetai! Iš jų vienas yra aktualus, o 3 turės būti išvalyti po jūsų [auto]VACUUM.

Nedarykite to tokiu būdu! Naudokite visų laukų atnaujinimas vienoje užklausoje — beveik visada metodo logiką galima pakeisti taip:

UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;

Nr. 2: naudojimas SKIRIASIS, Luke!

Taigi, jūs vis tiek norėjote atnaujinti daug, daug įrašų lentelėje (pvz., naudojant scenarijų arba konverterį). Ir į scenarijų patenka kažkas panašaus:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;

Maždaug tokios formos prašymas pateikiamas gana dažnai ir beveik visada ne užpildyti tuščią naują lauką, o ištaisyti kai kurias duomenų klaidas. Tuo pačiu ji pati visiškai neatsižvelgiama į esamų duomenų teisingumą - bet veltui! Tai reiškia, kad įrašas perrašomas, net jei jame yra būtent tai, ko norėjosi – bet kodėl? Pataisykime:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;

Daugelis žmonių nežino, kad egzistuoja toks nuostabus operatorius, todėl čia yra sukčiavimo lapas IS DISTINCT FROM ir kiti loginiai operatoriai, kurie padės:
PostgreSQL antipatterns: kova su „mirusiųjų“ miniomis
... ir šiek tiek apie komplekso operacijas ROW()-išraiškos:
PostgreSQL antipatterns: kova su „mirusiųjų“ miniomis

#3: Aš atpažįstu savo mylimąjį iš... blokavimo

Yra paleisti du identiški lygiagrečiai vykstantys procesai, kurių kiekvienas bando pažymėti įrašą, kad jis yra „vykdomas“:

UPDATE tbl SET processing = TRUE WHERE pk = $1;

Net jei šie procesai iš tikrųjų atlieka veiksmus nepriklausomai vienas nuo kito, bet naudojant tą patį ID, antrasis klientas bus „užrakintas“ pagal šią užklausą, kol bus atlikta pirmoji operacija.

1 sprendimas: užduotis sumažinama iki ankstesnės

Tiesiog pridėkime dar kartą IS DISTINCT FROM:

UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;

Šioje formoje antroji užklausa tiesiog nieko nepakeis duomenų bazėje, viskas jau yra taip, kaip turi būti – todėl blokavimas nebus. Toliau apdorojame įrašo „neradimo“ taikytame algoritme faktą.

2 sprendimas: patariamosios spynos

Didelė tema atskiram straipsniui, apie kurį galite perskaityti taikymo būdai ir rekomendacinio blokavimo „grėblys“..

3 sprendimas: kvaili skambučiai

Bet būtent taip ir turi nutikti tau vienu metu dirbti su tuo pačiu įrašu? O gal sujaukėte, pavyzdžiui, verslo logikos iškvietimo kliento pusėje algoritmus? O jei pagalvoji?..

Šaltinis: www.habr.com