MVCC-3. String bertsioak

Beraz, horri lotutako gaiak aztertu ditugu isolamendua, eta erretiro bat egin zuen datuak maila baxuan antolatzea. Eta azkenik, zatirik interesgarrienera iritsi ginen: kateen bertsioetara.

Izenburua

Esan dugunez, errenkada bakoitza aldi berean egon daiteke hainbat bertsiotan datu-basean. Bertsio bat nolabait beste batetik bereizi behar da.Horretarako, bertsio bakoitzak bi marka ditu, bertsio honen ekintzaren β€œdenbora” zehazten dutenak (xmin eta xmax). Komatxo artean - ez baita denbora gisa erabiltzen dena, gehikuntza-kontagailu berezi bat baizik. Eta kontagailu hau transakzio zenbakia da.

(Ohi bezala, errealitatea korapilatsuagoa da: transakzio-zenbakia ezin da denbora guztian handitu kontagailuaren bit-ahalmen mugatua dela eta. Baina xehetasun horiek zehatz-mehatz aztertuko ditugu izoztera iristen garenean.)

Errenkada bat sortzen denean, xmin INSERT komandoa eman duen transakzio-zenbakian ezartzen da eta xmax hutsik geratzen da.

Errenkada bat ezabatzen denean, uneko bertsioaren xmax balioa EZABATZEA egin duen transakzioaren zenbakiarekin markatzen da.

UPDATE komando baten bidez errenkada bat aldatzen denean, bi eragiketa egiten dira benetan: DELETE eta INSERT. Errenkadaren uneko bertsioak xmax ezartzen du EGUNERAZIOA egin duen transakzioaren kopuruaren berdina. Orduan kate beraren bertsio berri bat sortzen da; bere xmin balioa aurreko bertsioaren xmax balioarekin bat dator.

xmin eta xmax eremuak errenkadako bertsioaren goiburuan sartzen dira. Eremu horiez gain, goiburuak beste batzuk ere baditu, adibidez:

  • infomask bertsio honen propietateak definitzen dituen bit sorta da. Asko dira; Pixkanaka nagusienak kontuan hartuko ditugu.
  • ctid lerro bereko hurrengo bertsio berrirako esteka da. Kate baten bertsio berriena eta berriena, ctid-ak bertsio honi berari egiten dio erreferentzia. Zenbakiak forma du (x,y), non x orrialde-zenbakia den, y matrizeko indize-zenbakia den.
  • null bitmap - Balio nulua (NULL) duten bertsio jakin bateko zutabeak markatzen ditu. NULL ez da datu-motaren balio arruntetako bat, beraz, atributua bereizita gorde behar da.

Ondorioz, goiburua nahiko handia da - gutxienez 23 byte lerroaren bertsio bakoitzeko, eta normalean gehiago NULL bit-maparen ondorioz. Taula "estua" bada (hau da, zutabe gutxi baditu), gainkostuak informazio erabilgarria baino gehiago har dezake.

txertatzeko

Ikus ditzagun maila baxuko kateen eragiketak nola egiten diren gertutik, txertatzetik hasita.

Esperimentuak egiteko, sor dezagun taula berri bat bi zutabe eta horietako baten indize batekin:

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

Sar dezagun errenkada bat transakzio bat hasi ondoren.

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

Hona hemen gure transakzio-zenbakia:

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

Ikus ditzagun orriaren edukia. Pageinspect luzapenaren heap_page_items funtzioak erakusleei eta errenkada-bertsioei buruzko informazioa lortzeko aukera ematen du:

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

Kontuan izan PostgreSQL-n heap hitzak taulei egiten diela erreferentzia. Hau da terminoaren beste erabilera bitxi bat - pila bat ezagutzen da datuen egitura, mahaiarekin zerikusirik ez duena. Hemen hitza "dena batera botatzen da" zentzuan erabiltzen da, indize ordenatuen aurka.

Funtzioak datuak "dagoen moduan" erakusten ditu, ulertzen zaila den formatuan. Hori asmatzeko, informazioaren zati bat bakarrik utziko dugu eta deszifratuko dugu:

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

Hona hemen zer egin genuen:

  • Zero bat gehitu zaio indize-zenbakiari t_ctid-en itxura bera izan dezan (orriaren zenbakia, aurkibide-zenbakia).
  • Deszifratu lp_flags erakuslearen egoera. Hemen "normala" da - horrek esan nahi du erakusleak benetan katearen bertsioari egiten diola erreferentzia. Aurrerago beste esanahi batzuk aztertuko ditugu.
  • Informazio-bit guztietatik, bi bikote baino ez dira identifikatu orain arte. xmin_committed eta xmin_aborted bitek xmin transakzio-zenbakia konprometituta dagoen (abortu) adierazten dute. Antzeko bi bit xmax transakzio zenbakiari egiten diote erreferentzia.

Zer ikusten dugu? Errenkada bat txertatzen duzunean, 1 indize-zenbaki bat agertuko da taula-orrian, errenkadaren lehen bertsioa eta bakarra seinalatuz.

Katearen bertsioan, xmin eremua uneko transakzio-zenbakiarekin betetzen da. Transakzioa aktibo dago oraindik, beraz, xmin_committed eta xmin_aborted bitak ez daude ezarrita.

Errenkada bertsioa ctid eremuak errenkada berari egiten dio erreferentzia. Horrek esan nahi du ez dagoela bertsio berriagorik.

xmax eremua 0 zenbaki finko batekin betetzen da errenkadaren bertsio hau ezabatu ez delako eta unekoa delako. Transakzioek ez diote zenbaki honi kasurik egingo xmax_aborted bit-a ezarrita dagoelako.

Eman dezagun urrats bat gehiago irakurgarritasuna hobetzeko transakzio-zenbakiei informazio-bitak gehituz. Eta sor dezagun funtzio bat, eskaera behin baino gehiagotan beharko baitugu:

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

Inprimaki honetan, askoz argiago dago zer gertatzen den errenkadako bertsioaren goiburuan:

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

Antzeko informazioa, baina nabarmen txikiagoa dena, taulatik bertatik lor daiteke, xmin eta xmax sasi-zutabeak erabiliz:

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

Finkapena

Transakzio bat behar bezala betetzen bada, bere egoera gogoratu behar duzu - kontuan hartu konprometituta dagoela. Horretarako, XACT izeneko egitura erabiltzen da (eta 10. bertsioa baino lehen CLOG (commit log) deitzen zen eta izen hori oraindik leku ezberdinetan aurki daiteke).

XACT ez da sistema-katalogo-taula bat; hauek dira PGDATA/pg_xact direktorioko fitxategiak. Transakzio bakoitzerako bi bit esleituta dituzte: konprometitua eta abortatua - errenkadako bertsioaren goiburuan bezala. Informazio hau hainbat fitxategitan banatzen da erosotasunerako soilik; izoztea kontuan hartzen dugunean arazo honetara itzuliko gara. Eta fitxategi hauekin lana orrialdez orrialde egiten da, beste guztiek bezala.

Beraz, transakzio bat XACT-en konprometitzen denean, konprometitutako bita ezartzen da transakzio honetarako. Eta hau da konprometitzean gertatzen dena (nahiz eta oraindik ez garen aurre-grabaketaren erregistroaz hitz egiten).

Beste transakzio bat ikusi berri dugun taula-orrira sartzen denean, hainbat galdera erantzun beharko ditu.

  1. Amaitu al da xmin transakzioa? Hala ez bada, katearen sortutako bertsioa ez litzateke ikusgai egongo.
    Egiaztapen hau beste egitura bat aztertuz egiten da, instantziaren memoria partekatuan dagoena eta ProcArray deitzen dena. Prozesu aktibo guztien zerrenda dauka, eta bakoitzarentzat bere uneko transakzio (aktiboa) zenbakia adierazten da.
  2. Osatzen bada, nola - konprometituz edo bertan behera utziz? Bertan behera uzten bada, errenkadako bertsioak ere ez luke ikusgai egongo.
    Honetarako da zehazki XACT. Baina, XACT-ren azken orrialdeak RAM-eko bufferetan gordetzen diren arren, garestia da XACT egiaztatzea aldi bakoitzean. Hori dela eta, transakzioaren egoera zehaztutakoan, katearen bertsioaren xmin_committed eta xmin_aborted bitetan idazten da. Bit horietako bat ezartzen bada, xmin transakzioaren egoera ezagutzen da eta hurrengo transakzioak ez du XACT atzitu beharko.

Zergatik ez dira bit hauek transakzioak berak ezartzen txertaketa egiten? Txertaketa bat gertatzen denean, transakzioak oraindik ez daki arrakasta izango duen ala ez. Eta konpromisoa hartzeko momentuan, jada ez dago argi zein lerrotan zein orrialdetan aldatu ziren. Horrelako orrialde asko egon daitezke, eta horiek memorizatzea ez da errentagarria. Horrez gain, orrialde batzuk buffer cachetik diskora bota daitezke; berriro irakurtzeak bitak aldatzeko konpromisoa nabarmen motelduko luke.

Aurrezkiaren alde txarra da aldaketak egin ondoren, edozein transakzio (irakurketa sinple bat egiten duena ere - SELECT) buffer-eko cacheko datu-orriak aldatzen has daitekeela.

Beraz, konpon dezagun aldaketa.

=> COMMIT;

Ez da ezer aldatu orrialdean (baina badakigu transakzioaren egoera dagoeneko XACT-en erregistratuta dagoela):

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

Orain orrialdera lehenik sartzen den transakzioak xmin transakzioaren egoera zehaztu beharko du eta informazio-bitetan idatzi:

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

kentzea

Errenkada bat ezabatzen denean, uneko ezabatze-transakzioaren zenbakia uneko bertsioaren xmax eremuan idazten da eta xmax_aborted bit-a garbitzen da.

Kontuan izan transakzio aktiboari dagokion xmax-en ezarritako balioa errenkada-blokeo gisa jokatzen duela. Beste transakzio batek errenkada hau eguneratu edo ezabatu nahi badu, xmax transakzioa amaitu arte itxaron beharko du. Blokeatzeari buruz gehiago hitz egingo dugu gero. Oraingoz, errenkaden blokeo kopurua mugagabea dela ohartzen gara. Ez dute lekurik hartzen RAMan eta sistemaren errendimenduak ez du haien kopurua jasaten. Egia da, transakzio "luzeak" beste desabantaila batzuk ditu, baina gehiago gehiago geroago.

Ezabatu dezagun lerroa.

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

Transakzio-zenbakia xmax eremuan idatzita dagoela ikusten dugu, baina informazio-bitak ez daude ezarrita:

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

Baliogabetzea

Aldaketak bertan behera uzteak konprometitzearen antzera funtzionatzen du, XACT-en soilik transakziorako abortaturiko bita ezartzen da. Desegitea konpromisoa hartzea bezain azkarra da. Komandoari ROLLBACK deitzen zaion arren, aldaketak ez dira atzera egiten: transakzioak datu-orrietan aldatzea lortu duen guztia aldatu gabe geratzen da.

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

Orrialdera sartzen denean, egoera egiaztatuko da eta xmax_aborted hint bit-a errenkadako bertsioan ezarriko da. Xmax zenbakia bera orrialdean geratzen da, baina inork ez du begiratuko.

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

eguneratzea

Eguneratzeak lehen errenkadaren uneko bertsioa ezabatuko balu bezala funtzionatzen du eta gero berri bat sartuko balu bezala.

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

Kontsultak lerro bat sortzen du (bertsio berria):

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

Baina orrialdean bi bertsioak ikusten ditugu:

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

Ezabatutako bertsioa uneko transakzio-zenbakiarekin markatuta dago xmax eremuan. Gainera, balio hori zaharraren gainean idazten da, aurreko transakzioa bertan behera geratu baita. Eta xmax_aborted bit garbitu egiten da, uneko transakzioaren egoera oraindik ezagutzen ez delako.

Lerroaren lehen bertsioak orain bigarrenari (t_ctid eremua) berriagoa deritzo.

Bigarren indize bat agertzen da indize-orrian eta bigarren errenkada batek bigarren bertsioari erreferentzia egiten dio taula-orrian.

Ezabatzearekin gertatzen den bezala, errenkadaren lehen bertsioko xmax balioa errenkada blokeatuta dagoela adierazten du.

Beno, osa dezagun transakzioa.

=> COMMIT;

Indizeak

Orain arte taula-orriei buruz bakarrik hitz egin dugu. Zer gertatzen da indizeen barruan?

Aurkibide-orrietako informazioa asko aldatzen da indize mota zehatzaren arabera. Eta aurkibide mota batek ere orrialde mota desberdinak ditu. Adibidez, B-zuhaitz batek metadatuen orria eta orri "ohikoak" ditu.

Hala ere, orrialdeak normalean errenkadetarako eta errenkadetarako erakusle sorta bat izan ohi du (taula-orri batek bezala). Horrez gain, orriaren amaieran datu berezietarako lekua dago.

Indizeetako errenkadek ere oso egitura desberdinak izan ditzakete indize motaren arabera. Adibidez, B-zuhaitz baterako, hosto-orriekin erlazionatutako errenkadak indexatzeko gako-balioa eta dagokion taula-errenkadaren erreferentzia (ctid) dituzte. Oro har, indizea modu guztiz ezberdin batean egitura daiteke.

Garrantzitsuena da edozein motatako indizeetan ez dagoela errenkada bertsiorik. Beno, edo lerro bakoitza bertsio bakarrarekin adierazten dela suposa dezakegu. Beste era batera esanda, ez dago xmin eta xmax eremurik indizearen errenkadaren goiburuan. Indizeko estekek errenkaden taula-bertsio guztietara eramaten dutela pentsa dezakegu; beraz, transakzioak zein bertsio ikusiko duen taulari begiratuta bakarrik jakin dezakezu. (Beti bezala, hau ez da egia osoa. Zenbait kasutan, ikusgarritasun-mapak prozesua optimiza dezake, baina geroago aztertuko dugu zehatzago).

Aldi berean, aurkibide-orrian bi bertsioen erakusleak aurkituko ditugu, bai oraingoa bai zaharra:

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

Transakzio birtualak

Praktikan, PostgreSQL-k transakzio-zenbakiak "gordetzeko" aukera ematen duten optimizazioak erabiltzen ditu.

Transakzio batek datuak soilik irakurtzen baditu, ez du eraginik errenkadako bertsioen ikusgarritasunean. Hori dela eta, zerbitzu-prozesuak lehenik xid birtual bat ematen dio transakzioari. Zenbakia prozesu ID batek eta sekuentzia-zenbaki batek osatzen dute.

Zenbaki hau igortzeak ez du prozesu guztien arteko sinkronizazioa behar eta, beraz, oso azkarra da. Zenbaki birtualak erabiltzeko beste arrazoi bat ezagutuko dugu izozteari buruz hitz egiten dugunean.

Zenbaki birtualak ez dira inola ere kontuan hartzen datuen argazkietan.

Denbora ezberdinetan, baliteke sisteman transakzio birtualak egotea dagoeneko erabilitako zenbakiekin, eta hori normala da. Baina zenbaki hori ezin da datu-orrietan idatzi, orrialdera sartzen den hurrengo aldian esanahi guztia gal dezakeelako.

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

Transakzio bat datuak aldatzen hasten bada, transakzio-zenbaki erreal eta bakarra ematen zaio.

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

=> COMMIT;

Transakzio habiaratuak

Gorde puntuak

SQLn definitua gorde puntuak (gorde puntua), transakzio baten zati bat bertan behera uzteko aukera ematen dutenak, guztiz eten gabe. Baina hau ez da goiko diagraman sartzen, transakzioak egoera berdina baitu bere aldaketa guztietan, eta fisikoki ez da daturik atzera egiten.

Funtzionalitate hau ezartzeko, gordegune bat duen transakzio bat hainbat bereizitan banatzen da transakzio habiaratuak (azpitransakzioa), zeinaren egoera bereiz kudeatu daitekeen.

Habiaratutako transakzioek bere zenbakia dute (transakzio nagusiaren kopurua baino handiagoa). Habiaraturiko transakzioen egoera XACTen ohiko moduan erregistratzen da, baina azken egoera transakzio nagusiaren egoeraren araberakoa da: bertan behera uzten bada, habiaraturiko transakzio guztiak ere bertan behera geratzen dira.

Transakzioen habiratzeari buruzko informazioa PGDATA/pg_subtrans direktorioko fitxategietan gordetzen da. Fitxategiak instantziaren partekatutako memorian dauden bufferen bidez sartzen dira, XACT bufferen modu berean antolatuta.

Ez nahastu transakzio habiaratuak transakzio autonomoekin. Transakzio autonomoak ez dira bata bestearen menpekoak inolaz ere, baina habiaratuak bai. PostgreSQL arruntean ez dago transakzio autonomorik, eta, beharbada, onerako: oso-oso gutxitan behar dira, eta beste DBMS batzuetan egoteak tratu txarrak eragiten ditu, eta denek jasaten dute gero.

Garbitu dezagun taula, hasi transakzio bat eta txerta dezagun errenkada:

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

Orain jarri dezagun gordetzeko puntu bat eta txerta dezagun beste lerro bat.

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

Kontuan izan txid_current() funtzioak transakzio-zenbaki nagusia itzultzen duela, ez habiaraturiko transakzio-zenbakia.

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

Itzuli gordetzeko puntura eta txerta dezagun hirugarren lerroa.

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

Orrialdean bertan behera utzitako transakzio habiaratuak gehitutako errenkada ikusten jarraitzen dugu.

Aldaketak konpontzen ditugu.

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

Orain argi eta garbi ikus dezakezu habiaraturiko transakzio bakoitzak bere egoera duela.

Kontuan izan habiaraturiko transakzioak ezin direla esplizituki erabili SQL-n, hau da, ezin duzula transakzio berri bat hasi unekoa osatu gabe. Mekanismo hau inplizituki aktibatzen da gordeguneak erabiltzean, baita PL/pgSQL salbuespenak maneiatzen dituenean eta beste hainbat kasu exotikoagoetan ere.

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

Eragiketen akatsak eta atomotasuna

Zer gertatzen da eragiketa bat burutzean errore bat gertatzen bada? Adibidez, honela:

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

Akats bat gertatu da. Orain transakzioa abortatutzat hartzen da eta ez da eragiketarik onartzen:

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

Eta aldaketak konprometitzen saiatzen bazara ere, PostgreSQL-k eten baten berri emango du:

=> COMMIT;
ROLLBACK

Zergatik ezin da transakzio batek jarraitu huts egin ondoren? Kontua da akatsen bat sor litekeela aldaketen zati bat eskuratzeko aukera izango genukeela - transakzioaren atomotasuna ere ez, baina operadorea urratu egingo litzateke. Gure adibidean bezala, non operadoreak akatsaren aurretik lerro bat eguneratzea lortu zuen:

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

Esan beharra dago psql-ek hutsegite baten ondoren transakzioak jarraitzea ahalbidetzen duen modu bat duela, eragile okerraren ekintzak atzera botako balira bezala.

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

Ez da zaila asmatzea modu honetan, psql-k benetan gordetze-puntu inplizitu bat jartzen duela komando bakoitzaren aurretik, eta hutsegite kasuan, atzera buelta bat abiarazten duela. Modu hau ez da lehenespenez erabiltzen, gordetzeko puntuak ezartzeak (nahiz eta haietara itzuli gabe) gainkostu handia dakar.

Jarraipena.

Iturria: www.habr.com

Gehitu iruzkin berria