MVCC-3. Styginių versijos

Taigi, mes svarstėme klausimus, susijusius su izoliacija, ir atsitraukė apie duomenų tvarkymas žemu lygiu. Ir galiausiai priėjome prie įdomiausios dalies – styginių versijų.

Pavadinimas

Kaip jau minėjome, kiekviena eilutė vienu metu gali egzistuoti keliomis duomenų bazės versijomis. Viena versija turi būti kažkaip atskirta nuo kitos, todėl kiekviena versija turi du ženklus, kurie nustato šios versijos veikimo „laiką“ (xmin ir xmax). Kabutėse – nes naudojamas ne laikas kaip toks, o specialus didėjantis skaitiklis. Ir šis skaitiklis yra operacijos numeris.

(Kaip įprasta, realybė yra sudėtingesnė: operacijos skaičius negali visą laiką didėti dėl ribotos skaitiklio bitų talpos. Tačiau į šias detales pažvelgsime išsamiai, kai baigsime užšaldyti.)

Sukūrus eilutę, xmin nustatomas kaip operacijos numeris, išdavęs komandą INSERT, o laukelis xmax paliekamas tuščias.

Ištrynus eilutę, dabartinės versijos xmax reikšmė pažymima operacijos, kuri atliko DELETE, numeriu.

Kai eilutė pakeičiama komanda UPDATE, iš tikrųjų atliekamos dvi operacijos: DELETE ir INSERT. Dabartinė eilutės versija nustato xmax lygų operacijos, kuri atliko UPDATE, skaičiui. Tada sukuriama nauja tos pačios eilutės versija; jo xmin reikšmė sutampa su ankstesnės versijos xmax reikšme.

Laukai xmin ir xmax yra įtraukti į eilutės versijos antraštę. Be šių laukų, antraštėje yra kitų, pavyzdžiui:

  • infomask yra bitų serija, apibrėžianti šios versijos savybes. Jų yra gana daug; Palaipsniui apsvarstysime pagrindinius.
  • ctid yra nuoroda į kitą, naujesnę tos pačios eilutės versiją. Naujausioje, naujausioje eilutės versijoje ctid nurodo pačią šią versiją. Skaičius turi formą (x,y), kur x yra puslapio numeris, y yra indekso numeris masyve.
  • null bitmap – pažymi tuos tam tikros versijos stulpelius, kuriuose yra nulinė reikšmė (NULL). NULL nėra viena iš įprastų duomenų tipo reikšmių, todėl atributas turi būti saugomas atskirai.

Dėl to antraštė yra gana didelė – mažiausiai 23 baitai kiekvienai eilutės versijai ir dažniausiai daugiau dėl NULL bitmap. Jei lentelė yra „siaura“ (ty joje yra nedaug stulpelių), pridėtinės išlaidos gali užimti daugiau nei naudingos informacijos.

įterpti

Pažiūrėkime atidžiau, kaip atliekamos žemo lygio eilutės operacijos, pradedant nuo įterpimo.

Eksperimentams sukurkime naują lentelę su dviem stulpeliais ir indeksu viename iš jų:

=> CREATE TABLE t(
  id serial,
  s text
);
=> CREATE INDEX ON t(s);

Pradėję operaciją įterpkime vieną eilutę.

=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');

Štai mūsų dabartinis operacijos numeris:

=> SELECT txid_current();
 txid_current 
--------------
         3664
(1 row)

Pažiūrėkime į puslapio turinį. Plėtinio pageinspect funkcija heap_page_items leidžia gauti informacijos apie rodykles ir eilučių versijas:

=> SELECT * FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-------------------
lp          | 1
lp_off      | 8160
lp_flags    | 1
lp_len      | 32
t_xmin      | 3664
t_xmax      | 0
t_field3    | 0
t_ctid      | (0,1)
t_infomask2 | 2
t_infomask  | 2050
t_hoff      | 24
t_bits      | 
t_oid       | 
t_data      | x0100000009464f4f

Atminkite, kad žodis krūva PostgreSQL reiškia lenteles. Tai dar vienas keistas termino panaudojimas – žinoma krūva duomenų struktūra, kuris neturi nieko bendro su lentele. Čia žodis vartojamas reikšme „viskas sumesta“, o ne sutvarkyti indeksai.

Funkcija rodo duomenis tokius, kokie yra, sunkiai suprantamu formatu. Norėdami tai išsiaiškinti, paliksime tik dalį informacijos ir ją iššifruosime:

=> SELECT '(0,'||lp||')' AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin as xmin,
       t_xmax as xmax,
       (t_infomask & 256) > 0  AS xmin_commited,
       (t_infomask & 512) > 0  AS xmin_aborted,
       (t_infomask & 1024) > 0 AS xmax_commited,
       (t_infomask & 2048) > 0 AS xmax_aborted,
       t_ctid
FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-+-------
ctid          | (0,1)
state         | normal
xmin          | 3664
xmax          | 0
xmin_commited | f
xmin_aborted  | f
xmax_commited | f
xmax_aborted  | t
t_ctid        | (0,1)

Štai ką mes padarėme:

  • Prie indekso numerio pridėtas nulis, kad jis atrodytų taip pat kaip t_ctid: (puslapio numeris, indekso numeris).
  • Iššifruota lp_flags rodyklės būsena. Čia tai yra „normalus“ – tai reiškia, kad rodyklė iš tikrųjų nurodo eilutės versiją. Kitas reikšmes panagrinėsime vėliau.
  • Iš visų informacijos bitų iki šiol buvo nustatytos tik dvi poros. Bitai xmin_committed ir xmin_aborted rodo, ar operacijos numeris xmin yra įvykdytas (nutrauktas). Du panašūs bitai nurodo operacijos numerį xmax.

Ką mes matome? Kai įterpiate eilutę, lentelės puslapyje atsiras indekso numeris 1, nurodantis pirmąją ir vienintelę eilutės versiją.

Eilučių versijoje xmin laukas užpildomas dabartiniu operacijos numeriu. Operacija vis dar aktyvi, todėl bitai xmin_committed ir xmin_aborted nenustatyti.

Eilutės versijos ctid laukas nurodo tą pačią eilutę. Tai reiškia, kad naujesnės versijos nėra.

Laukas xmax užpildytas netikru numeriu 0, nes ši eilutės versija nebuvo ištrinta ir yra dabartinė. Operacijos nekreips dėmesio į šį skaičių, nes nustatytas bitas xmax_aborted.

Ženkime dar vieną žingsnį, kad pagerintume skaitomumą, pridėdami informacijos bitų prie operacijų numerių. Ir sukurkime funkciją, nes užklausos mums reikės daugiau nei vieną kartą:

=> CREATE FUNCTION heap_page(relname text, pageno integer)
RETURNS TABLE(ctid tid, state text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno,lp)::text::tid AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin || CASE
         WHEN (t_infomask & 256) > 0 THEN ' (c)'
         WHEN (t_infomask & 512) > 0 THEN ' (a)'
         ELSE ''
       END AS xmin,
       t_xmax || CASE
         WHEN (t_infomask & 1024) > 0 THEN ' (c)'
         WHEN (t_infomask & 2048) > 0 THEN ' (a)'
         ELSE ''
       END AS xmax,
       t_ctid
FROM heap_page_items(get_raw_page(relname,pageno))
ORDER BY lp;
$$ LANGUAGE SQL;

Šioje formoje daug aiškiau, kas vyksta eilutės versijos antraštėje:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Panašią, bet žymiai mažiau išsamią informaciją galima gauti iš pačios lentelės, naudojant pseudo stulpelius xmin ir xmax:

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3664 |    0 |  1 | FOO
(1 row)

Fiksavimas

Jei operacija sėkmingai užbaigta, turite atsiminti jos būseną – atkreipkite dėmesį, kad ji įvykdyta. Tam naudojama struktūra, vadinama XACT (o prieš 10 versiją ji vadinosi CLOG (įsipareigojimo žurnalas) ir šį pavadinimą vis dar galima rasti įvairiose vietose).

XACT nėra sistemos katalogo lentelė; tai failai kataloge PGDATA/pg_xact. Kiekvienai operacijai jie turi du bitus: įvykdytą ir nutrauktą – kaip ir eilutės versijos antraštėje. Ši informacija yra suskirstyta į kelis failus vien dėl patogumo; grįšime prie šios problemos, kai svarstysime galimybę įšaldyti. Ir darbas su šiais failais atliekamas puslapis po puslapio, kaip ir su visais kitais.

Taigi, kai operacija atliekama naudojant XACT, šiai operacijai nustatomas priskirtas bitas. Ir tai viskas, kas nutinka įsipareigojimo metu (nors apie išankstinio įrašymo žurnalą dar nekalbame).

Kai kita operacija pateks į lentelės puslapį, kurį ką tik peržiūrėjome, jis turės atsakyti į kelis klausimus.

  1. Ar xmin operacija baigta? Jei ne, tada sukurta eilutės versija neturėtų būti matoma.
    Šis patikrinimas atliekamas žiūrint į kitą struktūrą, kuri yra bendrojoje egzemplioriaus atmintyje ir vadinama ProcArray. Jame yra visų aktyvių procesų sąrašas, prie kiekvieno nurodomas jo esamos (aktyvios) operacijos numeris.
  2. Jei baigta, tai kaip – ​​įsipareigojant ar atšaukiant? Jei atšaukiama, eilutės versija taip pat neturėtų būti matoma.
    XACT būtent tam ir yra skirtas. Tačiau, nors paskutiniai XACT puslapiai yra saugomi RAM buferiuose, vis tiek brangu kiekvieną kartą patikrinti XACT. Todėl nustačius operacijos būseną, ji įrašoma į eilutės versijos bitus xmin_committed ir xmin_aborted. Jei nustatytas vienas iš šių bitų, tada operacijos xmin būsena laikoma žinoma ir kitai operacijai nereikės pasiekti XACT.

Kodėl šių bitų nenustato pati operacija, atliekanti įterpimą? Kai įvyksta įterpimas, operacija dar nežino, ar ji bus sėkminga. O įsipareigojimo momentu jau nebeaišku, kuriose eilutėse kuriuose puslapiuose buvo pakeista. Tokių puslapių gali būti daug, o įsiminti juos nepelninga. Be to, kai kurie puslapiai gali būti iškeldinti iš buferio talpyklos į diską; perskaičius juos dar kartą, kad pakeistumėte bitus, įsipareigojimas gerokai sulėtėtų.

Taupymo trūkumas yra tas, kad po pakeitimų bet kokia operacija (net ir atliekant paprastą skaitymą – SELECT) gali pradėti keisti duomenų puslapius buferio talpykloje.

Taigi, pataisykime pakeitimą.

=> COMMIT;

Puslapyje niekas nepasikeitė (tačiau žinome, kad operacijos būsena jau įrašyta XACT):

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Dabar operacija, kuri pirmiausia pasiekia puslapį, turės nustatyti xmin operacijos būseną ir įrašyti ją į informacijos bitus:

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 row)

Šalinimas

Kai eilutė ištrinama, dabartinės ištrynimo operacijos numeris įrašomas į dabartinės versijos xmax lauką, o bitas xmax_aborted išvalomas.

Atminkite, kad nustatyta xmax reikšmė, atitinkanti aktyvią operaciją, veikia kaip eilutės užraktas. Jei kita operacija nori atnaujinti arba ištrinti šią eilutę, ji bus priversta palaukti, kol operacija xmax bus baigta. Daugiau apie blokavimą kalbėsime vėliau. Kol kas tik atkreipiame dėmesį, kad eilučių užraktų skaičius neribojamas. Jie neužima vietos RAM, o sistemos veikimas nenukenčia nuo jų skaičiaus. Tiesa, „ilgi“ sandoriai turi ir kitų trūkumų, bet apie tai vėliau.

Ištrinkime eilutę.

=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
 txid_current 
--------------
         3665
(1 row)

Matome, kad operacijos numeris įrašytas xmax lauke, tačiau informacijos bitai nenustatyti:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Atšaukimas

Pakeitimų nutraukimas veikia panašiai kaip įsipareigojimas, tik XACT operacijai nustatomas nutrauktas bitas. Atšaukti yra taip pat greitai, kaip ir įsipareigoti. Nors komanda vadinama ROLLBACK, pakeitimai neatšaukiami: viskas, ką pavyko pakeisti per operaciją duomenų puslapiuose, lieka nepakitusi.

=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Kai bus atidarytas puslapis, būsena bus patikrinta, o užuominos bitas xmax_aborted bus nustatytas į eilutės versiją. Pats xmax numeris lieka puslapyje, bet niekas į jį nežiūrės.

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   |   xmax   | t_ctid 
-------+--------+----------+----------+--------
 (0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 row)

Atnaujinti

Atnaujinimas veikia taip, tarsi iš pradžių būtų ištrinta dabartinė eilutės versija, o po to būtų įterpta nauja.

=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
 txid_current 
--------------
         3666
(1 row)

Užklausa sukuria vieną eilutę (nauja versija):

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | BAR
(1 row)

Tačiau puslapyje matome abi versijas:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 3666  | (0,2)
 (0,2) | normal | 3666     | 0 (a) | (0,2)
(2 rows)

Ištrinta versija xmax laukelyje pažymėta esamu operacijos numeriu. Be to, ši vertė rašoma virš senosios, nes ankstesnė operacija buvo atšaukta. Ir bitas xmax_aborted išvalomas, nes dabartinės operacijos būsena dar nežinoma.

Pirmoji eilutės versija dabar nurodo antrąją (laukas t_ctid) kaip naujesnę.

Antroji rodyklė rodoma rodyklės puslapyje, o antroji eilutė nurodo antrąją lentelės puslapio versiją.

Kaip ir ištrynus, xmax reikšmė pirmoje eilutės versijoje rodo, kad eilutė užrakinta.

Na, užbaigkime sandorį.

=> COMMIT;

Indeksai

Kol kas kalbėjome tik apie lentelių puslapius. Kas vyksta indeksų viduje?

Informacija rodyklės puslapiuose labai skiriasi priklausomai nuo konkretaus indekso tipo. Ir net vieno tipo indeksai turi skirtingus puslapių tipus. Pavyzdžiui, B-medis turi metaduomenų puslapį ir „įprastus“ puslapius.

Tačiau puslapyje paprastai yra masyvas nuorodų į eilutes ir pačias eilutes (kaip ir lentelės puslapyje). Be to, puslapio pabaigoje yra vietos specialiems duomenims.

Indeksų eilutės taip pat gali turėti labai skirtingą struktūrą, priklausomai nuo indekso tipo. Pavyzdžiui, B medžio eilutėse, susijusiose su lapų puslapiais, yra indeksavimo rakto reikšmė ir nuoroda (ctid) į atitinkamą lentelės eilutę. Apskritai indeksas gali būti struktūrizuotas visiškai kitaip.

Svarbiausias dalykas yra tai, kad bet kokio tipo indeksuose nėra eilučių versijų. Na, arba galime daryti prielaidą, kad kiekviena eilutė pavaizduota tiksliai viena versija. Kitaip tariant, indekso eilutės antraštėje nėra laukų xmin ir xmax. Galime daryti prielaidą, kad nuorodos iš indekso veda į visas eilučių lentelės versijas – taigi, kurią versiją matys sandoris, galite sužinoti tik pažiūrėję į lentelę. (Kaip visada, tai nėra visa tiesa. Kai kuriais atvejais matomumo žemėlapis gali optimizuoti procesą, tačiau vėliau pažvelgsime į tai išsamiau.)

Tuo pačiu metu rodyklės puslapyje randame nuorodas į abi versijas, tiek dabartinę, tiek senąją:

=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
 itemoffset | ctid  
------------+-------
          1 | (0,2)
          2 | (0,1)
(2 rows)

Virtualios operacijos

Praktiškai PostgreSQL naudoja optimizavimus, leidžiančius „išsaugoti“ operacijų numerius.

Jei operacija skaito tik duomenis, tai neturi jokios įtakos eilučių versijų matomumui. Todėl aptarnavimo procesas pirmiausia išduoda virtualų xid operacijai. Skaičius susideda iš proceso ID ir eilės numerio.

Šio numerio išdavimas nereikalauja visų procesų sinchronizavimo, todėl yra labai greitas. Su dar viena virtualių numerių naudojimo priežastimi susipažinsime, kai kalbėsime apie užšalimą.

Duomenų momentinėse nuotraukose jokiu būdu neatsižvelgiama į virtualius skaičius.

Skirtingais laiko momentais sistemoje gali būti virtualių operacijų su jau panaudotais numeriais, ir tai normalu. Bet tokio numerio negalima įrašyti į duomenų puslapius, nes kitą kartą einant į puslapį jis gali prarasti prasmę.

=> BEGIN;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

Jei operacijos duomenys pradeda keistis, jai suteikiamas tikras, unikalus operacijos numeris.

=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 row)

=> COMMIT;

Įdėtos operacijos

Išsaugokite taškus

Apibrėžiama SQL taupyti taškus (savepoint), kurios leidžia atšaukti dalį operacijos jos visiškai nenutraukiant. Tačiau tai netelpa į aukščiau pateiktą diagramą, nes operacijos būsena yra tokia pati visiems jos pakeitimams ir fiziškai jokie duomenys neatšaukiami.

Norint įgyvendinti šią funkciją, operacija su išsaugojimo tašku yra padalinta į keletą atskirų įdėtos operacijos (suboperacija), kurios būsena gali būti valdoma atskirai.

Įdėtos operacijos turi savo numerį (didesnį nei pagrindinės operacijos numeris). Įdėtųjų operacijų būsena XACT įrašoma įprastu būdu, tačiau galutinė būsena priklauso nuo pagrindinės operacijos būsenos: jei ji atšaukiama, tuomet atšaukiamos ir visos įdėtos operacijos.

Informacija apie operacijų įdėjimą saugoma failuose kataloge PGDATA/pg_subtrans. Failai pasiekiami per egzemplioriaus bendrinamos atminties buferius, sutvarkytus taip pat, kaip ir XACT buferiai.

Nepainiokite įdėtųjų operacijų su savarankiškomis operacijomis. Autonominės operacijos niekaip nepriklauso viena nuo kitos, tačiau įdėtos operacijos priklauso. Įprastame „PostgreSQL“ nėra savarankiškų transakcijų ir, ko gero, geriausia: jų reikia labai, labai retai, o jų buvimas kitose DBVS išprovokuoja piktnaudžiavimą, nuo kurio tada kenčia visi.

Išvalykime lentelę, pradėkime operaciją ir įterpkime eilutę:

=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3669 | 0 (a) | (0,1)
(1 row)

Dabar pastatykime išsaugojimo tašką ir įterpkime kitą eilutę.

=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

Atminkite, kad funkcija txid_current() grąžina pagrindinį operacijos numerį, o ne įdėto operacijos numerį.

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3670 |    0 |  3 | XYZ
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3669 | 0 (a) | (0,1)
 (0,2) | normal | 3670 | 0 (a) | (0,2)
(2 rows)

Grįžkime į išsaugojimo tašką ir įterpkime trečią eilutę.

=> ROLLBACK TO sp;
=> INSERT INTO t(s) VALUES ('BAR');
=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3671 |    0 |  4 | BAR
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669     | 0 (a) | (0,1)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671     | 0 (a) | (0,3)
(3 rows)

Puslapyje ir toliau matome eilutę, pridėtą atšauktos įdėtos operacijos.

Taisome pakeitimus.

=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3671 |    0 |  4 | BAR
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669 (c) | 0 (a) | (0,1)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(3 rows)

Dabar aiškiai matote, kad kiekviena įdėta operacija turi savo būseną.

Atminkite, kad įdėtos operacijos negali būti tiesiogiai naudojamos SQL, tai yra, negalite pradėti naujos operacijos neužbaigę dabartinės. Šis mechanizmas netiesiogiai įjungiamas naudojant išsaugojimo taškus, taip pat tvarkant PL/pgSQL išimtis ir daugeliu kitų, egzotiškesnių atvejų.

=> BEGIN;
BEGIN
=> BEGIN;
WARNING:  there is already a transaction in progress
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WARNING:  there is no transaction in progress
COMMIT

Klaidos ir operacijų atomiškumas

Kas atsitiks, jei atliekant operaciją įvyksta klaida? Pavyzdžiui, taip:

=> BEGIN;
=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR:  division by zero

Įvyko klaida. Dabar operacija laikoma nutraukta ir joje neleidžiama atlikti jokių operacijų:

=> SELECT * FROM t;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Ir net jei bandysite atlikti pakeitimus, PostgreSQL praneš apie nutraukimą:

=> COMMIT;
ROLLBACK

Kodėl po nesėkmės sandoris negali tęstis? Faktas yra tas, kad klaida gali atsirasti taip, kad mes gautume prieigą prie dalies pakeitimų – būtų pažeistas net ne sandorio, o operatoriaus atomiškumas. Kaip ir mūsų pavyzdyje, kur operatoriui pavyko atnaujinti vieną eilutę prieš klaidą:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669 (c) | 3672  | (0,4)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671 (c) | 0 (a) | (0,3)
 (0,4) | normal | 3672     | 0 (a) | (0,4)
(4 rows)

Reikia pasakyti, kad psql turi režimą, kuris vis tiek leidžia operaciją tęsti po gedimo taip, tarsi klaidingo operatoriaus veiksmai būtų atšaukti.

=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR:  division by zero

=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> COMMIT;

Nesunku atspėti, kad šiuo režimu psql prieš kiekvieną komandą įdeda numanomą išsaugojimo tašką, o gedimo atveju inicijuoja jos atšaukimą. Šis režimas pagal numatytuosius nustatymus nenaudojamas, nes įrašymo taškų nustatymas (net ir jų negrįžus) reikalauja didelių išlaidų.

Tęsinys.

Šaltinis: www.habr.com

Добавить комментарий