MVCC-3. Ridade versioonid

Nii, oleme kĂ€sitlenud teemasid, mis on seotud isoleerimisega, ja teinud kĂ”rvalepĂ”ike madala taseme andmete korraldamisse. Ja lĂ”puks jĂ”udsime kĂ”ige huvitavamani — stringiversioonideni.

Pealkiri

Nagu me juba mainisime, vĂ”ib iga string vestelda andmebaasis samal ajal mitmes versioonis. Üks versioon tuleb teisest kuidagi eristada. Selleks on igal versioonil kaks mĂ€rget, mis mÀÀravad selle versiooni kehtivuse „aja“ (xmin ja xmax). Kutsume seda „ajaks”, kuna tegelikult ei kasutata aega sellisel kujul, vaid spetsiaalset kasvavat loendurit. Ja see loendur on transaktsiooni number.

(Nagu tavaliselt, on asi tegelikult keerulisem: transaktsioonide number ei saa pidevalt suureneda, kuna loenduri tĂ€psus on piiratud. Kuid neid ĂŒksikasju kĂ€sitleme ĂŒksikasjalikult, kui jĂ”uame kĂŒlmutamise teemadeni.)

Kui string luuakse, siis xmin vÀÀrtus mÀÀratakse transaktsiooni numbriks, mis tÀitis INSERT kÀsu, ja xmax ei tÀidetud.

Kui string kustutatakse, siis praeguse versiooni xmax vÀÀrtus mÀrgitakse kustutamise teinud transaktsiooni numbriga.

Kui stringi muudetakse UPDATE kÀsuga, siis tehakse tegelikult kaks operatsiooni: KUSTUTA ja SISSESEDA. Praeguse stringi versioonis mÀÀratakse xmax, mis vÔrdub transaktsiooni numbriga, mis tÀitis UPDATE. SeejÀrel luuakse sama stringi uus versioon; xmin vÀÀrtus sellel on sama, mis eelneva versiooni xmax.

VÀli xmin ja xmax kuuluvad stringi versiooni pealkirja. Lisaks nendele vÀljadele sisaldab pealkiri ka teisi, nÀiteks:

  • infomask — bitti rida, mis mÀÀratleb selle versiooni omadused. Nende arv on ĂŒsna suur; peamised neist kĂ€sitleme jĂ€rk-jĂ€rgult.
  • ctid — viide jĂ€rgmisele, uuemale versioonile sama stringi kohta. KĂ”ige uuema, kehtiva stringi versioon viitab sellele versioonile. Nummer on kujul (x,y), kus x on lehe number, y on viite jĂ€rjekorranumber massiivis.
  • mitteteadlike vÀÀrtuste bitikaart — tĂ€histab neid veerge, millel on selle versiooni puhul teadmata vÀÀrtus (NULL). NULL ei ole ĂŒks tavapĂ€rastest andmetĂŒĂŒpide vÀÀrtustest, seega tuleb tĂ€histamine salvestada eraldi.

Seega on pealkiri ĂŒsna suur — vĂ€hemalt 23 baiti iga stringi versiooni kohta, ja tavaliselt rohkem NULL-i bitikaardi tĂ”ttu. Kui tabel on „kitsas” (st sisaldab vĂ€he veerge), vĂ”ivad halduskoormused olla suuremad kui kasulik teave.

Sisestamine

Vaatame lÀhemalt, kuidas madala taseme stringioperatsioonid toimuvad ja alustame sisestamisest.

Katsetamiseks loome uue tabeli, millel on kaks veergu ja indeks ĂŒhe neist:

=> LOOMA TABEL t(
  id seeria,
  s tekst
);
=> LOOMA INDEKS ON t(s);

Sisestame ĂŒhe rea, alustades eelnevalt tehingut.

=> ALUSTAMA;
=> SISSESTAMA t(s) VÄÄRTUS ('FOO');

Siin on meie praeguse tehingu nummer:

=> VALI txid_current();
 txid_current 
--------------
         3664
(1 rida)

Vaata saiti sisu. Laienduse pageinspect funktsioon heap_page_items vÔimaldab saada teavet ridade viidete ja versioonide kohta:

=> VALI * FROM heap_page_items(get_raw_page('t',0)) gx
-[ KANDID 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

Pange tĂ€hele, et PostgreSQL kasutab sĂ”na heap (kupatus) tabelite tĂ€histamiseks. See on veel ĂŒks kummaline kasutusviis — kupatus on tuttav andmestruktuur,, millel pole tabeliga midagi ĂŒhist. Siin kasutatakse seda sĂ”na tĂ€henduses "kĂ”ik on kuhjatud kuhja", erinevalt korrastatud indeksitest.

Funktsioon nÀitab andmeid "nii nagu nad on", formaadis, mis on raskesti mÔistetav. Et lahti mÔtestada, jÀÀme vaid osa teabest ja tÔlgime selle:

=> VALI '(0,'||lp||')' NIMEKS ctid,
       JUHUL lp_flags
         KUI 0 SIIS 'kasutamata'
         KUI 1 SIIS 'tavaline'
         KUI 2 SIIS 'suunata '||lp_off
         KUI 3 SIIS 'surmav'
       LÕPP NIMEKS olek,
       t_xmin kui xmin,
       t_xmax kui xmax,
       (t_infomask & 256) > 0  NIMETA xmin_commited,
       (t_infomask & 512) > 0  NIMETA xmin_aborted,
       (t_infomask & 1024) > 0 NIMETA xmax_commited,
       (t_infomask & 2048) > 0 NIMETA xmax_aborted,
       t_ctid
FROM heap_page_items(get_raw_page('t',0)) gx
-[ KANDID 1 ]-+-------
ctid          | (0,1)
olek         | tavaline
xmin          | 3664
xmax          | 0
xmin_commited | f
xmin_aborted  | f
xmax_commited | f
xmax_aborted  | t
t_ctid        | (0,1)

See on, mida me tegime:

  • Lisatud viite numbrisse null, et see oleks samas vormis nagu t_ctid: (lehe number, viite number).
  • TĂ”lgitud viite lp_flags olek. Siin on see 'tavaline' — see tĂ€hendab, et viide viitab tĂ”eliselt rea versioonile. Teised vÀÀrtused vaatame hiljem.
  • Kogu informatiivsete bittide hulgast eraldasime praegu ainult kaks paari. Bittide xmin_committed ja xmin_aborted nĂ€itavad, kas xmin numbriga tehing on kinnitatud (tĂŒhistatud). Kaks sarnast bitti kuuluvad xmax numbriga tehingule.

Mida me nÀeme? Kui rida tabelile lisatakse, kuvatakse viit nr 1, mis viitab reas olevale esimesele ja ainukesele versioonile.

Rea versioonis on vÀli xmin tÀidetud praeguse tehingu numbriga. Tehing on endiselt aktiivne, seega ei ole bits xmin_committed ega xmin_aborted seadistatud.

Rea versiooni ctid vÀli viitab samale reale. See tÀhendab, et uuemat versiooni ei eksisteeri.

VÀli xmax on tÀidetud vale numbriga 0, kuna selle rea versioon ei ole kustutatud ja on aktiivne. Tehingud ei pööra sellele numbrile tÀhelepanu, kuna xaborted bit on seadistatud.

Liikume samm edasi, et parandada loetavust, lisades tehingu numbri juurde teavet. Ja loome funktsiooni, kuna see pÀring tuleb meil kasutada veel korduvalt:

=> 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 'kasutamata'
         WHEN 1 THEN 'tavaline'
         WHEN 2 THEN 'suunatud '||lp_off
         WHEN 3 THEN 'surnud'
       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;

NĂŒĂŒd on palju selgem, mis toimub rea versiooni pĂ€ises:

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

Sarnast, kuid oluliselt vÀhem detailset teavet on vÔimalik saada ka otse tabelist, kasutades pseudoveerge xmin ja xmax:

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

Kinnitus

Tehingu eduka lĂ”puleviimise korral tuleb meeles pidada selle staatust — mĂ€rkida, et see on kinnitatud. Selleks kasutatakse struktuuri, mida nimetatakse XACT (ja versioonis 10 oli see tuntud kui CLOG (commit log) ning seda nime vĂ”ib endiselt leida mitmest kohast).

XACT — ei ole sĂŒsteemi katalooge; need on failid PGDATA/pg_xact kataloogis. Igale tehingule on eraldatud kaks bitti: committed ja aborted — tĂ€pselt nagu rea versiooni pĂ€ises. See teave on jaotatud mitmesse faili, et see oleks mugavam, ja me tuleme selle kĂŒsimuse juurde tagasi, kui arutame kĂŒlmutamist. Ja nende failidega töötamine toimub lehekaupa, nagu ka kĂ”igi teistega.

Nii et, kui tehing on XACT-is fikseeritud, seatakse vastava tehingu jaoks bit committed. Ja see on kÔik, mis fikseerimise kÀigus juhtub (kuigi me ei rÀÀgi veel ettekirjutuse pÀevikust).

Kui mĂ”ni teine tehing pöördub tabeli lehe poole, mida me just vaatasime, peab see vastama mitmele kĂŒsimusele.

  1. Kas tehing on xmin lÔppenud? Kui ei, ei peaks loodud rida olema nÀhtav.
    Selline kontroll toimub, vaadates teist struktuuri, mis asub instantsi jagatud mĂ€lus ja mida nimetatakse ProcArray'ks. See sisaldab kĂ”igi aktiivsete protsesside loendit ja igaĂŒhe jaoks on mĂ€rgitud tema praegune (aktiivne) tehing.
  2. Kui lĂ”ppes, siis kuidas — fikseerimise vĂ”i tĂŒhistamise teel? Kui tĂŒhistamise teel, siis ei peaks rida samuti nĂ€htav olema.
    Just selle jaoks ongi XACT vajalik. Kuid kuigi viimased XACT lehed salvestatakse mÀlus, on iga kord XACTi kontrollimine kulukas. SeetÔttu salvestatakse kord leidud tehingu staatus ridade versioonides bittides xmin_committed ja xmin_aborted. Kui mÔni neist bittidest on seatud, peetakse xmin tehingu seisundit teadaolevaks ja jÀrgmisel tehingul ei ole enam vaja pöörduda XACT'i poole.

Miks neid bitte ei sea tehing, mis sisestust teeb? Kui sisestamine toimub, ei tea tehing veel, kas see lÔpeb edukalt. Ja fikseerimise hetkel ei ole selge, millised read ja millistes lehtedes on muutunud. Selliseid lehti vÔib olla palju ja nende meelespidamine ei ole kasulik. Lisaks vÔivad mÔned lehed olla vahemÀlust kettale tÔrjutud; nende uuesti lugemine, et muuta bitte, tÀhendaks fikseerimise mÀrkimisvÀÀrset aeglustumist.

Kulu kokkuhoiu tĂ”eliseks tagajĂ€rjeks on see, et muudatuste jĂ€rel vĂ”ib iga tehing (isegi kĂ”ige lihtsam lugemine — SELECT) alustada andmelehtede muutmist vahemĂ€lus.

Nii et fikseerime muudatuse.

=> COMMIT;

Lehes ei ole midagi muutunud (aga me teame, et tehingu seisund on juba XACT-i salvestatud):

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

NĂŒĂŒd peab esmalt lehe poole pöördunud tehing mÀÀrama xmin tehingu staatuse ja salvestab selle informatiivsetesse bittidesse:

=> 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)

Kustutamine

Kui rida xmax vÀljale eemaldatakse, salvestatakse praeguse eemaldava tehingu number ja bit xmax_aborted nullitakse.

TĂ€helepanu, et seadistatud vÀÀrtus xmax, mis vastab aktiivsele tehingule, toimib rida blokeerimisena. Kui teine tehing kavatseb seda rida uuendada vĂ”i eemaldada, peab see ootama tehingu xmax lĂ”ppu. Rikkumiste kohta rÀÀgime hiljem. Seni mĂ€rgime ainult, et ridade blokeeringute arv ei ole millegiga piiratud. Need ei hĂ”ivata mĂ€lu ja sĂŒsteemi jĂ”udlus ei kannata nende arvu tĂ”ttu. TĂ”si, „pika” tehingu puhul on muid puudusi, kuid sellest rÀÀgime ka hiljem.

Eemaldame rea.

=> ALGUS;
=> KUSTUTA t;
=> VALI txid_current();
 txid_current 
--------------
         3665
(1 rida)

NÀeme, et tehingu number on salvestatud xmax vÀlja, kuid teabitsad ei ole seadistatud:

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

TĂŒhistamine

Muudatuste tĂŒhistamine töötab sarnaselt fikseerimisega, ainult et XACT-is seadistatakse tehingule bit aborted. TĂŒhistamine toimub sama kiiresti kui fikseerimine. Kuigi kĂ€sk nimetatakse ROLLBACK, ei toimu muudatuste tagasivĂ”tmist: kĂ”ik, mida tehing jĂ”udis andmelehtedes muuta, jÀÀb muutumatuks.

=> ROLLBACK;
=> VALI * HEAP_PAGE('t',0);
 ctid  | olek  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normaalne | 3664 (c) | 3665 | (0,1)
(1 rida)

Lehe juurde pöördumisel kontrollitakse olekut ja rida versioonis seadistatakse bit mÀrgistus xmax_aborted. Xmax number jÀÀb lehele, kuid keegi ei vaata seda enam.

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

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

Upgrade

Uuendamine töötab nii, et esmalt nÀivad olevat eemaldatud praeguse versiooni rida ja seejÀrel sisestatud uus.

=> ALGUS;
=> Uuenda t SET s = 'BAR';
=> VALI txid_current();
 txid_current 
--------------
         3666
(1 rida)

KĂŒsi annab ĂŒhe reani (uue versiooni):

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

Kuid lehel nÀeme kahte versiooni:

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

Eemaldatud versioon on mĂ€rgitud praeguse tehingu numbri abil xmax vĂ€ljale. Lisaks on see vÀÀrtus salvestatud vana peale, kuna eelmine tehing tĂŒhistati. Ja bit xmax_aborted nullitati, kuna praeguse tehingu olek on veel teadmata.

Esimene versioon rida viitab nĂŒĂŒd teisele (vĂ€ljavĂ€lja t_ctid), kui uuele.

Indeksilehel kuvatakse teine viit ja teine rida, mis osutab teisele versioonile tabelilehel.

Nagu ka kustutamisel, toimib rida maksimaalne vÀÀrtus xmax esimeses versioonis mÀrkina, et rida on lukustatud.

Ja lÔpetame tehingu.

=> COMMIT;

Indeksid

Kuni nĂŒĂŒd oleme rÀÀkinud ainult tabelilehtedest. Mis aga juhtub indeksites?

Teave indeksilehtede kohta sĂ”ltub tugevasti konkreetse indeksi tĂŒĂŒbist. Isegi ĂŒhe indeksi tĂŒĂŒbi puhul vĂ”ivad olla erinevad lehtede liigid. NĂ€iteks B-puul on metaandmete leht ja 'tavalised' lehed.

Sellegipoolest sisaldab leht tavaliselt massiivi viiteid ridadele ja neid ridasid (nagu ka tabelilehe puhul). Lisaks on lehe lÔpus ruum spetsiaalsete andmete jaoks.

Indeksides vĂ”ivad read samuti olla vĂ€ga erineva struktuuriga, olenevalt indeksi tĂŒĂŒbist. NĂ€iteks B-puul sisaldavad lehtedele kuuluvad read indekseeritava vĂ”tme vÀÀrtust ja viidet (ctid) vastavale tabeli reale. Üldiselt vĂ”ib indeks olla ĂŒles ehitatud tĂ€iesti teistsugusel viisil.

Oluline on see, et ĂŒkskĂ”ik millise tĂŒĂŒbi indeksites ei ole rida versioone. VĂ”i vĂ”ib öelda, et igat rida esindab tĂ€pselt ĂŒks versioon. TeisisĂ”nu, indeksirea pealkirjas ei ole vĂ€lju xmin ja xmax. VĂ”ib öelda, et viidud indeksid viivad kĂ”ikide tabeliversioonide ridadeni — seega saab aru, millist versiooni tehing nĂ€eb, ainult tabelisse vaatates. (Nagu tavaliselt, see ei ole kogu tĂ”de. MĂ”nel juhul vĂ”imaldab nĂ€htavuse kaart protsessi optimeerida, kuid seda vaatame hiljem pĂ”hjalikult.)

Samas leidub indeksilehelt viiteid mÔlemale versioonile, nii praegusele kui ka vanemale:

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

Virtuaalsed tehingud

Praktikas kasutab PostgreSQL optimeerimist, mis vÔimaldab 'sÀÀsta' tehingute numbreid.

Kui tehing ainult loeb andmeid, ei mÔjuta see ridade versioonide nÀhtavust. SeetÔttu annab teenindusprotsess alguses tehingule virtuaalse numbri (virtual xid). Number koosneb protsessi identifikaatorist ja jÀrjestikulisest numbrist.

Selle numbri vĂ€ljastamine ei nĂ”ua sĂŒnkroniseerimist kĂ”igi protsesside vahel, mistĂ”ttu toimub see vĂ€ga kiiresti. Teise pĂ”hjusena virtuaalsete numbrite kasutamiseks tutvume, kui rÀÀgime kĂŒlmutamisest.

Virtuaalsed numbrid ei kajastu andmete hetkepiltides.

Erinevatel aegadel vĂ”ivad sĂŒsteemis olla virtuaalsed tehingud numbritega, mida on juba kasutatud, ja see on normaalne. Kuid sellist numbrit ei tohi andmelehtedele kirjutada, sest jĂ€rgmise kĂ”ne korral vĂ”ib see kaotada igasuguse tĂ€henduse.

=> ALGUS;
=> VALI txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 rida)

Kui tehing hakkab andmeid muutma, antakse talle tÔeline, unikaalne tehingu number.

=> Uuenda kontod SET summa = summa - 1.00;
=> VALI txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 rida)

=> COMMIT;

Sisemised tehingud

Salvestuspunktid

SQL-is on mÀÀratletud salvestuspunktid (savepoint), mis vĂ”imaldavad tĂŒhistada osa tehingu toimingutest, katkestamata seda tĂ€ielikult. Kuid see ei sobi ĂŒlaltoodud skeemiga, kuna tehingu olek on kĂ”igi selle muudatuste jaoks ĂŒhine ja fĂŒĂŒsiliselt ei tĂŒhistata mingeid andmeid.

Selle funktsiooni rakendamiseks jagatakse salvestuspunktiga tehing mitmeks eraldi sisemiseks tehinguks (subtransaction), mille staatust saab eraldi hallata.

Sisemistel tehingutel on oma number (suurem kui pĂ”hitehingu number). Sisemiste tehingute olekut salvestatakse tavalisel viisil XACT-is, kuid lĂ”plik olek sĂ”ltub pĂ”hitehingu olekust: kui see tĂŒhistatakse, tĂŒhistatakse ka kĂ”ik sisemised tehingud.

Teave tehingute sisemuse kohta salvestatakse failides PGDATA/pg_subtrans katalooge. Failidele pÀÀseb ligi eksemplari jagatud mĂ€lu puhversĂŒsteemide kaudu, mis on organiseeritud samamoodi nagu XACT-i puhversĂŒsteemid.

Ärge segage sisemisi tehinguid ja iseseisvaid tehinguid. Iseseisvad tehingud ei sĂ”ltu ĂŒksteisest, samas kui sisemised tehingud — sĂ”ltuvad. Ükski tavaline PostgreSQL ei sisaldanud iseseisvaid tehinguid ja see on tĂ”enĂ€oliselt parem: neid on tegelikult vĂ€ga harva vaja ning nende olemasolu teistes andmebaasisĂŒsteemides soodustab kuritarvitamist, mille tĂ”ttu kannatavad kĂ”ik hiljem.

Kustutame tabeli, alustame tehingut ja lisame rea:

=> 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)

NĂŒĂŒd seadke salvestuspunkt ja lisage veel ĂŒks rida.

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

MÀrka, et funktsioon txid_current() annab vÀlja peamise, mitte pesastatud tehingu numbri.

=> 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)

Tagasi minnes salvestuspunktist ning lisades kolmanda rea.

=> 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)

Lehelt nĂ€eme jĂ€tkuvalt rida, mis lisati tĂŒhistatud pesastatud tehingu kaudu.

Fikseerime muudatused.

=> 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)

NĂŒĂŒd on hĂ€sti nĂ€ha, et igal pesastatud tehingul on oma olek.

MÀrka, et pesastatud tehingute selgesÔnaline kasutamine SQL-is ei ole lubatud, s.t. uut tehingut ei saa alustada, kui praegune pole lÔpetatud. Seda mehhanismi rakendatakse kaudselt salvestuspunktide kasutamisel ning PL/pgSQL erandite töötlemise vÀltimisel ja mitmete muude, eksootilisemate juhtumite korral.

=> BEGIN;
BEGIN
=> BEGIN;
HOIATUS:  tehing on juba kÀimas
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
HOIATUS:  tehingut ei toimu
COMMIT

Vead ja operatsioonide atomicsus

Mis juhtub, kui operatsiooni kÀigus esineb viga? NÀiteks nii:

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

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

Viga. NĂŒĂŒd loetakse tehing tĂŒhistatuks ja selles ei lubata ĂŒhtegi operatsiooni:

=> SELECT * FROM t;
ERROR:  praegune tehing on katkenud, kÀsud ignoreeritakse kuni tehingu ploki lÔpuni

Ja isegi kui proovida muudatusi fikseerida, teatab PostgreSQL tĂŒhistamisest:

=> COMMIT;
ROLLBACK

Miks ei saa tehingut pĂ€rast viga jĂ€tkata? Asi on selles, et viga vĂ”is tekkida nii, et me oleksime saanud osa muudatustest — oleks rikutud isegi mitte tehingu, vaid operaatori aatomilisust. Nagu meie nĂ€ites, kus operaator jĂ”udis enne viga uuendada ĂŒhe rea:

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

Tuleb mĂ€rkida, et psql-il on reĆŸiim, mis siiski vĂ”imaldab jĂ€tkata tehingu töötamist pĂ€rast viga, nagu oleks vigane operaatori tegevus tagasi vĂ”etud.

=> 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:  jagamine nulliga

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

=> COMMIT;

Pole raske arvata, et sellises reĆŸiimis seab psql tegelikult iga kĂ€su ette mitteavaliku salvestuskoha ja juhul, kui toimub viga, algatab see tagasiviimise sinna. Sellist reĆŸiimi ei kasutata vaikimisi, kuna salvestuskohtade seadmine (isegi ilma nendele tagastamiseta) toob kaasa mĂ€rkimisvÀÀrseid kulusid.

JĂ€tkamine.

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