MVCC-3. Ridade versioonid

Nii, oleme vaadanud kĂŒsimusi, mis on seotud isoleerimisega, ja tegime kĂ”rvalepĂ”ike andmete korraldusest madalal tasemel. Ja lĂ”puks oleme jĂ”udnud kĂ”ige huvitavama juurde — ridade versioonid.

PĂ€is

Kuidas juba mainitud, vĂ”ib iga rida samaaegselt olemas olla andmebaasis mitmes versioonis. Üksteisest tuleb versioonid millegipĂ€rast eristada. Selleks on igal versioonil olemas kaks mĂ€rki, mis mÀÀravad selle versiooni „kehtivuse” aja (xmin ja xmax). Juttu on „ajast”, kuna tegelikult ei kasutata aega niivĂ”rd, vaid erilist kasvavat arvestit. See arvesti on tehingu number.

(Nagu tavaliselt, on tegelikult kĂ”ik keerulisem: tehingu numbrid ei saa pidevalt suureneda piiratud bitisĂŒgavuse tĂ”ttu. Kuid need detailid kĂ€sitleme pĂ”hjalikult, kui jĂ”uame kĂŒlmutamise juurde.)

Kui rida luuakse, seadistatakse xmin vÀÀrtus tehingu numbriks, mis tegi INSERT kÀsu, samas kui xmax ei tÀideta.

Kui rida kustutatakse, mÀrgistatakse praeguse versiooni xmax vÀÀrtus kustutamise teinud tehingu numbriga.

Kui rida muudetakse UPDATE kÀsuga, toimub tegelikult kaks toimingut: KUSTUTA ja INSERT. Rida seadistatakse praeguses versioonis xmax-iga, mis on tehingu number, mis teostas UPDATE. SeejÀrel luuakse sama rea uus versioon; sellel on xmin vÀÀrtus, mis vastab eelneva versiooni xmax-ile.

VÀljad xmin ja xmax sisalduvad rea versiooni pealkirjas. Lisaks nendele vÀljadele sisaldab pealkiri ka teisi, nÀiteks:

  • infomask — bitide rida, mis mÀÀratleb antud versiooni omadused. Neid on ĂŒsna palju; peamised neist vaatame jĂ€rk-jĂ€rgult ĂŒle.
  • ctid — viidatud jĂ€rgmise, uuema versiooni sama rea kohta. KĂ”ige uuema, aktiivse, versiooni ctid viitab sellele samale versioonile. Number on kujul (x,y), kus x on lehe number ja y on viite jĂ€rjestusnumber massiivis.
  • mÀÀramatute vÀÀrtuste bitikaart — mĂ€rgib need veerud antud versioonis, mis sisaldavad mÀÀramatut vÀÀrtust (NULL). NULL ei ole ĂŒks tavalisi andmetĂŒĂŒpe, seetĂ”ttu tuleb omadust hoida eraldi.

SeetĂ”ttu on pealkiri ĂŒsna suur — iga rea versiooni kohta vĂ€hemalt 23 bitti, tavaliselt rohkem tĂ€nu NULL-bitikaardile. Kui tabel on "kitsas" (st sisaldab vĂ€heseid veerge), vĂ”ivad kulud ĂŒletada kasulikku teavet.

Sisestamine

Vaatame lÀhemalt, kuidas reaoperatsioonid madalamal tasemel toimuvad, ja alustame sisestamisest.

Eksperimentide jaoks loome uue tabeli, kus on kaks veergu ja indeks ĂŒhe neist jĂ€rgi:

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

Sisestame ĂŒhe rea, alustades tehingut.

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

Siin on meie praeguse tehingu number:

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

Vaadakem lehe sisu. Funktsioon heap_page_items laiendamisest pageinspect vÔimaldab saada teavet nÀidikute ja ridade versioonide kohta:

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

TĂ€htis on mĂ€rkida, et PostgreSQL'is tĂ€histab sĂ”na heap (virn) tabeleid. See on veel ĂŒks kummaline kasutusviis, kuna virn on tuntud andmestruktuur,, mis ei oma tabeliga midagi ĂŒhist. Siin kasutatakse seda sĂ”na tĂ€henduses «kĂ”ik on kuhjatud kokku», erinevalt jĂ€rjestatud indeksitest.

Funktsioon nÀitab andmeid «nagu nad on», raskesti tajutavas formaadis. Selgusele jÔudmiseks jÀtame alles vaid osa teabest ja dekodeerime selle:

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

Siin on, mida me tegime:

  • Lisades osutaja numbrile nulli, et viia see samasugusele kujule nagu t_ctid: (lehe number, osutaja number).
  • Oleme deĆĄifreerinud lp_flags nĂ€itaja oleku. Siin on see 'normal' — see tĂ€hendab, et nĂ€itaja viitab tĂ”epoolest rea versioonile. Teisi vÀÀrtusi arutame hiljem.
  • Oleme seni esile tĂ”stnud ainult kaks paari teavet. Bitid xmin_committed ja xmin_aborted nĂ€itavad, kas tehing numbriga xmin on kinnitatud (vĂ”i tĂŒhistatud). Kaks sarnast bitti puudutavad tehingut numbriga xmax.

Mida me nÀeme? Kui rida sisestatakse tabeli lehele, tekib viit number 1, mis viitab reaalne esimesele ja ainule versioonile.

Rea versioonis on vÀli xmin tÀidetud praeguse tehingu numbriga. Tehing on endiselt aktiivne, seega mÔlemad bitti xmin_committed ja xmin_aborted ei ole seatud.

Rea versiooni ctid viitab samale reale. See tÀhendab, et uut versiooni ei eksisteeri.

VÀli xmax on tÀidetud vale numbri 0-ga, kuna see rea versioon ei ole kustutatud ja on aktuaalne. Tehingud ei pööra sellele numbrile tÀhelepanu, kuna bit xmax_aborted on seatud.

Teeme veel ĂŒhe sammu loetavuse parandamiseks, lisades tehingute numbrite juurde teavet. Loome funktsiooni, kuna see pĂ€ring on meil veel mitmel korral vajalik:

=> LOOMI FUNKTSIOON 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 'normal'
         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;

Selline vorm on palju arusaadavam, mis juhtub rea versiooni pÀises:

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

Sarnase, kuid oluliselt vÀhem detailse, teabe saab ka otse tabelist, kasutades pseudokoldeid xmin ja xmax:

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

Fikseerimine

Kui tehing on edukalt lĂ”petatud, tuleb meeles pidada selle staatust — mĂ€rkida, et see on fikseeritud. Selleks kasutatakse struktuuri, mida nimetatakse XACT (ja enne versiooni 10 nimetati seda CLOG (commit log), ja see nimetus vĂ”ib endiselt ilmneda erinevates kohtades).

XACT — ei ole sĂŒsteemi katalooge sisaldav tabel; need on failid kataloogis PGDATA/pg_xact. Iga tehingu jaoks on seal eraldatud kaks bitti: committed ja aborted — just nagu ridade versioonide pĂ€ises. See teave on jagatud mitmeks failiks puhtalt mugavuse huvides, me naaseme selle kĂŒsimuse juurde, kui kĂ€sitleme kĂŒlmutamist. Ja nende failidega töötamine toimub lehekaupa, nagu ka kĂ”ikide teistega.

Nii et tehingu kinnitamise ajal XACT-is seatakse selle tehingu jaoks committed bitti. Ja see on kÔik, mis kinnitamise ajal juhtub (kuigi me ei rÀÀgi veel logi eelregistreerimisest).

Kui mĂ”ni muu tehing pöördub tabelilehekĂŒlje poole, mida me just vaatasime, peab see vastama mitmele kĂŒsimusele.

  1. Kas tehing xmin on lÔppenud? Kui ei, siis loodud rida ei tohiks olla nÀhtav.
    Selline kontroll toimub veel ĂŒhe struktuuri vaatamise teel, mis asub instantsi jagatud mĂ€lus ja mida nimetatakse ProcArray. Seal on loetelu kĂ”igist aktiivsetest protsessidest, ning igaĂŒhe jaoks on nĂ€idatud tema praeguse (aktiivse) tehingu number.
  2. Kui see on lĂ”ppenud, siis kuidas — fikseerimise vĂ”i tĂŒhistamise kaudu? Kui tĂŒhistamise kaudu, siis ei peaks versiooni rida ka nĂ€htav olema.
    Just selleks ongi vajalik XACT. Kuigi XACTi viimased lehed sĂ€ilitatakse mĂ€llu puhverdatult, on XACTi kontrollimine iga kord kulukas. SeetĂ”ttu salvestatakse kord juba tuvastatud tehingu olek bitidesse xmin_committed ja xmin_aborted versiooni reas. Kui ĂŒks neist bittidest on seatud, loetakse tehingu olek xmin teada olevaks ja jĂ€rgmine tehing ei pea enam XACTi poole pöörduma.

Miks neid bitte ei seadista tehing, mis tÀidab sisestamise? Kui toimub sisestamine, ei tea tehing veel, kas see lÔpetatakse edukalt. Ja fikseerimise hetkel ei ole selge, millised read millistes lehtedes on muudetud. Selliseid lehti vÔib olla palju ning nende meeles pidamine ei ole kasulik. Lisaks vÔivad osad lehed puhverdatud mÀlust kettale vÀlja tÔrjuda; nende uuesti lugemine, et bitte muuta, tÀhendaks fikseerimise oluliselt aeglustumist.

Kulu tĂ”usmine seisneb selles, et pĂ€rast muudatusi vĂ”ivad kĂ”ik tehingud (isegi lihtsa lugemise — SELECT korral) hakata muutma andmelehti vahemĂ€lus.

Kinnitame muudatuse.

=> COMMIT;

Lehel ei ole midagi muutunud (aga me teame, et tehingu staatus on juba salvestatud XACT-isse):

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

NĂŒĂŒd peab tehing, mis esimesena lehele pöördus, mÀÀrama tehingu staatuse xmin ja salvestama selle teabelehtedes:

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

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

Kustuta

Reakustutamisel salvestatakse praeguse kustutava tehingu number xmax aktiivse versiooni vÀljal, ning xmax_aborted bitti resettitakse.

Kuna xmax, mis vastab aktiivsele tehingule, toimib rea lukuna. Kui mĂ”ni teine tehing ĂŒritab seda rida uuendada vĂ”i kustutada, peab ta ootama xmaxi tehingu lĂ”petamist. Lukkudest rÀÀgime hiljem. Praegu tuleb mĂ€rkida, et rea lukustuste arv pole millegagi piiratud. Need ei vĂ”ta ruumi mĂ€lus ja sĂŒsteemi jĂ”udlus ei kannata nende arvu tĂ”ttu. TĂ”si, "pikkadel" tehingutel on teised puudused, aga sellest rÀÀgime hiljem.

Kustutame rea.

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

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

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

TĂŒhistamine

Muudatuste tĂŒhistamine töötab sarnaselt fikseerimisele, ainult et XACTis mÀÀratakse tehingule abort bit. TĂŒhistamine toimub sama kiiresti kui fikseerimine. Kuigi kĂ€sk nimetatakse ROLLBACK, ei toimu muudatuste tagasivĂ”tmist: kĂ”ik, mida tehing suutis andmelehtedele muuta, jÀÀb muutumatuks.

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

Lehe kutsumise ajal kontrollitakse olekut, ja ridade versioonile seatakse abiteave xmax_aborted. Samas jÀÀb xmax number lehele, kuid keegi ei vaata sellele enam.

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

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

Uuendamine

Uuendamine töötab nii, nagu oleks alguses praeguse rida versiooni eemaldamine toimunud, seejÀrel uue lisamine.

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

KĂ€sk annab ĂŒhe rea (uue versiooni):

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

Kuid lehe peal nÀeme mÔlemat versiooni:

=> VALI * 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 numbriga vĂ€ljale xmax. Ja see vÀÀrtus on kirjutatud vana peale, kuna eelmine tehing oli tĂŒhistatud. Samuti on xmax_aborted bit lĂ€htestatud, kuna praeguse tehingu olek on veel teadmata.

Esimene rida versioon viitab nĂŒĂŒd teisele (vĂ€ljal t_ctid), nagu uuele.

Indeksi lehelt ilmub teine viide ja teine rida, mis viitab teisele versioonile tabelilehel.

Nagu ka kustutamisel, on xmax esimese versiooni reas mÀrk, et rida on lukustatud.

KokkuvÔttes lÔpetame tehingu.

=> COMMIT;

Indeksid

Siiani oleme rÀÀkinud ainult tabelilehtedest. Mis juhtub indeksites?

Teave indeksilehtedes sĂ”ltub palju konkreetse indeksi tĂŒĂŒbist. Ja isegi ĂŒhel indeksi tĂŒĂŒbil on erinevaid lehe tĂŒĂŒpe. NĂ€iteks B-puul on metainfot sisaldav leht ja 'tavalised' lehed.

Tavaliselt sisaldab leht viidete massiivi ridadele ja iseendale (nagu ka tabelilehel). Lisaks on lehe lÔpus reserveeritud ruum spetsiaalsetele andmetele.

Indeksites olevatel ridades vĂ”ib samuti olla vĂ€ga erinev struktuur sĂ”ltuvalt indeksi tĂŒĂŒbist. NĂ€iteks B-puul sisaldavad lehtedele kuuluvad read indekseerimise vĂ”tme vÀÀrtust ja viidet (ctid) vastavale tabeli reale. Üldiselt vĂ”ib indeks olla korraldatud tĂ€iesti erinevalt.

Oluline on mĂ€rkida, et igasugustes indeksites ei ole ridade versioone. VĂ”i siis vĂ”ib öelda, et iga rida on esitatud tĂ€pselt ĂŒhe versioonina. TeisisĂ”nu, indeksirida ei sisalda vĂ€lju xmin ega xmax. VĂ”ib jĂ€reldada, et indeksi viidatud lingid viivad kĂ”ikide tabeli ridade versioonideni — seega saab aru saada, millist versiooni tehing nĂ€eb, vaid vaatates tabelisse. (Kuidas tavaliselt, see pole kogu tĂ”de. MĂ”nedes olukordades vĂ”imaldab nĂ€htavuse kaart protsesside optimeerimist, kuid vaatame seda hiljem lĂ€hemalt.)

Samas lemmekandja lehelt leiame viidatud mÔlema versiooni, nii aktiivse kui ka vana:

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

Virtuaalsed tehingud

Tavaliselt kasutab PostgreSQL optimeerimist, mis vÔimaldab "sÀÀsta" tehingu numbreid.

Kui tehing ainult loeb andmeid, siis ei mÔjuta see ridade versioonide nÀhtavust. Seega alguses vÀljastab teenindusprotsess tehingutele virtuaalse numbri (virtual xid). Number koosneb protsessi identifikaatorist ja jÀrjestikustest numbritest.

Selle numbri vĂ€ljastamine ei vaja kĂ”igi protsesside vahel sĂŒnkroniseerimist ja toimub seetĂ”ttu vĂ€ga kiiresti. Teise pĂ”hjusega virtuaalsete numbrite kasutamiseks tutvume siis, kui rÀÀgime kĂŒlmutamisest.

Virtuaalsed numbrid ei arvesta andmevÔtetel.

Ajahetkedel vĂ”ivad sĂŒsteemis olla virtuaalsed tehingud numbritega, mida on juba kasutatud, ja see on normaalne. Kuid sellist numbrit ei tohi andmehalduse lehtedes salvestada, kuna jĂ€rgmisel pöördumisel lehele vĂ”ib see kaotada igasuguse tĂ€henduse.

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

Kui tehing hakkab andmeid muutma, antakse sellele tÔeline, ainulaadne tehingunumber.

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

=> COMMIT;

Sisemised tehingud

Salvestuspunktid

SQL-is mÀÀratletud salvestuspunktid (savepoint), mis vĂ”imaldavad tagasi vĂ”tta osa tehingu toimest, katkestamata seda tĂ€ielikult. Kuid see ei vasta ĂŒlaltoodud skeemile, kuna tehingu olek on kĂ”igi selle muudatuste jaoks ĂŒhtne ja fĂŒĂŒsiliselt ei tagastata mingeid andmeid.

Selle funktsionaalsuse rakendamiseks jagatakse tehing koos salvestuspunktiga mitmeks eraldi sisemiseks tehinguks (subtransaction), mille olekut saab hallata eraldi.

Sisemistel tehingutel on oma numbrid (suurem kui pĂ”hitehingu number). Sisemise tehingu olek salvestatakse tavapĂ€raselt XACT-is, kuid lĂ”plik olek sĂ”ltub pĂ”hitehingu olekust: kui see on tĂŒhistatud, tĂŒhistatakse ka kĂ”ik sisemise tehingud.

Tehingute sisemuse teave salvestatakse failides PGDATA/pg_subtrans kataloogis. Failidele pÀÀseb juurde eksemplari jagatud mÀlu puhvrite kaudu, mis on korraldatud nagu XACT-i puhvrites.

Ärge segage sissekandetud tehingute ja iseseisvate tehingute vahel. Iseseisvad tehingud ei sĂ”ltu ĂŒksteisest, samas kui sissekandetud tehingud sĂ”ltuvad. Tavalises PostgreSQL-is pole iseseisvaid tehinguid, ja see on kindlasti parem: neid on tĂ”eliselt harva vaja ning nende olemasolu teistes andmebaasisĂŒsteemides kutsub esile kuritarvitusi, mis mĂ”jutavad kĂ”iki.

Kustutame tabeli, alustame tehingut ja lisame rea:

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

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

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

NĂŒĂŒd paneme tahvlipunkti ja sisestame veel ĂŒhe rea.

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

Pange tÀhele, et funktsioon txid_current() annab vÀlja pÔhitehingu, mitte sissekandetud tehingu numbri.

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

=> VALI * 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 rida)

Tagastame tahvlipunkti ja lisame 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 rida)

=> VALI * FROM heap_page('t',0);
 ctid  | seisund |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normaal | 3669     | 0 (a) | (0,1)
 (0,2) | normaal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normaal | 3671     | 0 (a) | (0,3)
(3 rida)

LehekĂŒljel nĂ€eme jĂ€tkuvalt rida, mis on lisatud tĂŒhistatud sisedoandega.

Salvestame muudatused.

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

=> VALI * FROM heap_page('t',0);
 ctid  | seisund |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normaal | 3669 (c) | 0 (a) | (0,1)
 (0,2) | normaal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normaal | 3671 (c) | 0 (a) | (0,3)
(3 rida)

NĂŒĂŒd on hĂ€sti nĂ€ha, et iga sisemine tehing omab oma staatust.

TÀhtis on mÀrkida, et SQL-i sees ei saa kasutada sisedoandeid, see tÀhendab, et ei saa alustada uut tehingut, lÔpetamata praegust. See mehhanism aktiveeritakse vargsi salvestuspunktide kasutamisel, samuti PL/pgSQL eksimuste töötlemisel ja mitmetes teistes, eksootilisemates olukordades.

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

Vead ja operatsioonide aatomiseerimine

Mis juhtub, kui operatsiooni teostamisel ilmneb viga? NĂ€iteks nii:

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

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

Tekkis viga. NĂŒĂŒd loetakse tehing peatatud ja selles ei lubata ĂŒhtegi toimingut:

=> SELECT * FROM t;
VIGA: praegune tehing on tĂŒhistatud, kĂ€sud jĂ€etakse tĂ€helepanuta kuni tehingu bloki lĂ”puni

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

=> COMMIT;
RULLI TAGASI

Miks ei saa tehingu tĂ€itmist pĂ€rast viga jĂ€tkata? Asi on selles, et viga vĂ”is tekkida nii, et saaksime osa muudatustest — oleks rikutud mitte ainult tehingu, vaid ka operaatori aatomilisust. Nagu meie nĂ€ites, kus operaator suutis enne viga uuendada ĂŒhte rida:

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

Peab ĂŒtlema, et psql-is on reĆŸiim, mis siiski vĂ”imaldab tehingu jĂ€tkamist pĂ€rast viga, just nagu oleks vale operaatori toimingud tagasi rullitud.

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

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

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

=> COMMIT;

Ei ole raske mĂ”ista, et sellises reĆŸiimis seab psql igas kĂ€sus sisuliselt vaikimisi pÀÀstekoha, ja tĂ”rke korral algatab see tagasi kerimise. Sellist reĆŸiimi ei kasutata vaikimisi, kuna pÀÀstekohtade seadmine (isegi ilma tagasi kerimata) toob kaasa mĂ€rkimisvÀÀrseid kulusid.

JĂ€tkub.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster