MVCC-3. Ŝnuraj versioj

Do, ni pripensis aferojn rilatajn al izolado, kaj faris retiriĝon ĉirkaŭ organizante datumoj je malalta nivelo. Kaj finfine ni alvenis al la plej interesa parto - la kordversioj.

kaplinio

Kiel ni jam diris, ĉiu vico povas ekzisti samtempe en pluraj versioj en la datumbazo. Unu versio devas esti iel distingita de alia.Por tiu ĉi celo ĉiu versio havas du markojn kiuj determinas la "tempon" de ago de tiu ĉi versio (xmin kaj xmax). Inter citiloj - ĉar ne la tempo kiel tia estas uzata, sed speciala kreskanta nombrilo. Kaj ĉi tiu nombrilo estas la transakcia numero.

(Kiel kutime, la realo estas pli komplika: la transakcia nombro ne povas pliiĝi la tutan tempon pro la limigita bita kapablo de la nombrilo. Sed ni rigardos ĉi tiujn detalojn detale kiam ni alvenos al frosto.)

Kiam vico estas kreita, xmin estas agordita al la transakcia numero kiu eldonis la INSERT-komandon, kaj xmax estas lasita malplena.

Kiam vico estas forigita, la xmax-valoro de la nuna versio estas markita kun la nombro de la transakcio, kiu faris la FORIGON.

Kiam vico estas modifita per UPDATE-komando, du operacioj efektive estas faritaj: DELETE kaj INSERT. La nuna versio de la vico fiksas xmax egala al la nombro de la transakcio, kiu faris la ĜISDATON. Nova versio de la sama ŝnuro tiam estas kreita; ĝia xmin valoro koincidas kun la xmax valoro de la antaŭa versio.

La xmin kaj xmax-kampoj estas inkluzivitaj en la vica versio-kapo. Krom ĉi tiuj kampoj, la kaplinio enhavas aliajn, ekzemple:

  • infomasko estas serio de bitoj, kiuj difinas la ecojn de ĉi tiu versio. Estas sufiĉe multe da ili; Ni iom post iom konsideros la ĉefajn.
  • ctid estas ligilo al la sekva, pli nova versio de la sama linio. Por la plej nova, plej aktuala versio de ĉeno, la ctid rilatas al ĉi tiu versio mem. La nombro havas la formon (x,y), kie x estas la paĝnumero, y estas la indeksa nombro en la tabelo.
  • nula bitmapo - Markas tiujn kolumnojn de donita versio, kiuj enhavas nulan valoron (NULL). NULL ne estas unu el la normalaj datumtipvaloroj, do la atributo devas esti konservita aparte.

Kiel rezulto, la kaplinio estas sufiĉe granda - almenaŭ 23 bajtoj por ĉiu versio de la linio, kaj kutime pli pro la NULL-bitmapo. Se la tabelo estas "malvasta" (tio estas, enhavas malmultajn kolumnojn), la supraĵo povas preni pli ol la utilaj informoj.

enigi

Ni rigardu pli detale kiel malaltnivelajn kordoperaciojn estas faritaj, komencante per enmeto.

Por eksperimentoj, ni kreu novan tabelon kun du kolumnoj kaj indekso sur unu el ili:

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

Ni enigu unu vicon post komenci transakcion.

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

Jen nia nuna transakcia numero:

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

Ni rigardu la enhavon de la paĝo. La funkcio heap_page_items de la etendaĵo pageinspect permesas vin akiri informojn pri montriloj kaj vicversioj:

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

Notu, ke la vorto amaso en PostgreSQL rilatas al tabeloj. Jen alia stranga uzo de la termino - amaso estas konata datumstrukturo, kiu havas nenion komunan kun la tablo. Ĉi tie la vorto estas uzata en la senco de "ĉio estas kunĵetita", kontraste al ordigitaj indeksoj.

La funkcio montras datumojn "kiel estas", en formato malfacile komprenebla. Por eltrovi ĝin, ni lasos nur parton de la informoj kaj deĉifros ĝin:

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

Jen kion ni faris:

  • Aldonis nulon al la indeksa nombro por ke ĝi aspektu same kiel t_ctid: (paĝnumero, indeksa nombro).
  • Ĉifris la staton de la lp_flags-montrilo. Ĉi tie ĝi estas "normala" - tio signifas, ke la montrilo fakte rilatas al la versio de la ĉeno. Ni rigardos aliajn signifojn poste.
  • El ĉiuj informbitoj, nur du paroj estis identigitaj ĝis nun. La bitoj xmin_committed kaj xmin_aborted indikas ĉu transakcinombro xmin estas farita (interrompita). Du similaj bitoj rilatas al transakcia nombro xmax.

Kion ni vidas? Kiam vi enmetas vicon, indeksa numero 1 aperos en la tabelpaĝo, montrante la unuan kaj nuran version de la vico.

En la ĉenversio, la xmin-kampo estas plenigita kun la nuna transakcia numero. La transakcio ankoraŭ estas aktiva, do ambaŭ la bitoj xmin_committed kaj xmin_aborted ne estas agordita.

La vica versio ctid-kampo rilatas al la sama vico. Ĉi tio signifas, ke pli nova versio ne ekzistas.

La xmax-kampo estas plenigita kun ia numero 0 ĉar ĉi tiu versio de la vico ne estis forigita kaj estas aktuala. Transakcioj ne atentos ĉi tiun nombron ĉar la xmax_aborted bito estas metita.

Ni faru unu plian paŝon al plibonigo de legebleco aldonante informbitojn al transakciaj nombroj. Kaj ni kreu funkcion, ĉar ni bezonos la peton pli ol unufoje:

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

En ĉi tiu formo, estas multe pli klare, kio okazas en la kaplinio de la vica versio:

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

Similaj, sed signife malpli detalaj, informoj povas esti akiritaj de la tabelo mem, uzante pseŭdo-kolumnojn xmin kaj xmax:

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

Fiksado

Se transakcio estas sukcese finita, vi devas memori ĝian staton - notu, ke ĝi estas farita. Por fari tion oni uzas strukturon nomatan XACT (kaj antaŭ la versio 10 ĝi nomiĝis CLOG (commit log) kaj ĉi tiu nomo ankoraŭ troviĝas en diversaj lokoj).

XACT ne estas sistema katalogo-tabelo; ĉi tiuj estas la dosieroj en la dosierujo PGDATA/pg_xact. Ili havas du bitojn por ĉiu transakcio: farita kaj abortita - same kiel en la vica versio-kapo. Ĉi tiu informo estas dividita en plurajn dosierojn nur por oportuno; ni revenos al ĉi tiu afero kiam ni pripensos frostigon. Kaj laboro kun ĉi tiuj dosieroj estas farita paĝo post paĝo, kiel ĉe ĉiuj aliaj.

Do, kiam transakcio estas farita en XACT, la farita bito estas metita por ĉi tiu transakcio. Kaj ĉi tio estas ĉio, kio okazas dum kompromiso (kvankam ni ankoraŭ ne parolas pri la antaŭregistrada protokolo).

Kiam alia transakcio aliras la tabelpaĝon, kiun ni ĵus rigardis, ĝi devos respondi plurajn demandojn.

  1. Ĉu la xmin-transakcio finiĝis? Se ne, tiam la kreita versio de la ĉeno ne estu videbla.
    Ĉi tiu kontrolo estas farita rigardante alian strukturon, kiu situas en la komuna memoro de la kazo kaj nomiĝas ProcArray. Ĝi enhavas liston de ĉiuj aktivaj procezoj, kaj por ĉiu estas indikita la nombro de ĝia nuna (aktiva) transakcio.
  2. Se kompletigite, tiam kiel - per kompromiso aŭ nuligo? Se nuligite, tiam ankaŭ la vicversio ne estu videbla.
    Ĝuste por tio estas XACT. Sed, kvankam la lastaj paĝoj de XACT estas konservitaj en bufroj en RAM, estas ankoraŭ multekoste kontroli XACT ĉiufoje. Tial, post kiam la transakcia stato estas determinita, ĝi estas skribita al la xmin_committed kaj xmin_aborted bitoj de la ĉenversio. Se unu el ĉi tiuj bitoj estas metita, tiam la stato de transakcio xmin estas konsiderata konata kaj la sekva transakcio ne devos aliri XACT.

Kial ĉi tiuj bitoj ne estas fiksitaj de la transakcio mem farante la enmeton? Kiam enmeto okazas, la transakcio ankoraŭ ne scias ĉu ĝi sukcesos. Kaj en la momento de farado, ne plu estas klare, kiuj linioj en kiuj paĝoj estis ŝanĝitaj. Eble ekzistas multaj tiaj paĝoj, kaj enmemorigi ilin estas neprofita. Krome, kelkaj paĝoj povas esti elpelitaj de la bufrokaŝmemoro al disko; legi ilin denove por ŝanĝi la bitojn malrapidigus la kommit signife.

La malavantaĝo de la ŝparaĵoj estas, ke post ŝanĝoj, ajna transakcio (eĉ unu elfaranta simplan legadon - SELECT) povas komenci ŝanĝi datumpaĝojn en la bufrokaŝmemoro.

Do, ni riparu la ŝanĝon.

=> COMMIT;

Nenio ŝanĝiĝis en la paĝo (sed ni scias, ke la transakcia stato jam estas registrita en XACT):

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

Nun la transakcio, kiu unue aliras la paĝon, devos determini la xmin-transakcian statuson kaj skribi ĝin al la informaj bitoj:

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

Forigi

Kiam vico estas forigita, la nombro de la nuna foriga transakcio estas skribita al la xmax-kampo de la nuna versio, kaj la xmax_aborted-bito estas forigita.

Notu, ke la fiksita valoro de xmax responda al la aktiva transakcio funkcias kiel vica seruro. Se alia transakcio volas ĝisdatigi aŭ forigi ĉi tiun vicon, ĝi estos devigita atendi ke transakcio xmax finiĝos. Ni parolos pli pri blokado poste. Nuntempe ni nur rimarkas, ke la nombro da vicaj seruroj estas senlima. Ili ne okupas spacon en RAM kaj sistema rendimento ne suferas pro sia nombro. Vere, "longaj" transakcioj havas aliajn malavantaĝojn, sed pli pri tio poste.

Ni forigu la linion.

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

Ni vidas, ke la transakcia numero estas skribita en la kampo xmax, sed la informaj bitoj ne estas fiksitaj:

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

nuligo

Aborti ŝanĝojn funkcias simile al transigado, nur en XACT la interrompita bito estas metita por la transakcio. Malfari estas tiel rapida kiel fari. Kvankam la komando nomiĝas ROLLBACK, ŝanĝoj ne estas retrovigitaj: ĉio, kion la transakcio sukcesis ŝanĝi en la datumpaĝoj, restas senŝanĝa.

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

Kiam la paĝo estas alirita, la stato estos kontrolita kaj la xmax_aborted sugesto estos agordita al la vica versio. La xmax nombro mem restas sur la paĝo, sed neniu rigardos ĝin.

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

Ĝisdatigu

La ĝisdatigo funkcias kvazaŭ ĝi unue forigis la nunan version de la vico kaj poste enigis novan.

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

La demando produktas unu linion (nova versio):

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

Sed sur la paĝo ni vidas ambaŭ versiojn:

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

La forigita versio estas markita per la nuna transakcia numero en la kampo xmax. Krome, ĉi tiu valoro estas skribita super la malnova, ĉar la antaŭa transakcio estis nuligita. Kaj la xmax_aborted bito estas forigita ĉar la stato de la nuna transakcio ankoraŭ ne estas konata.

La unua versio de la linio nun rilatas al la dua (t_ctid-kampo) kiel la pli nova.

Dua indekso aperas en la indeksa paĝo kaj dua vico referencas la duan version en la tabelpaĝo.

Same kiel ĉe forigo, la xmax-valoro en la unua versio de la vico estas indiko, ke la vico estas ŝlosita.

Nu, ni kompletigu la transakcion.

=> COMMIT;

Indeksoj

Ĝis nun ni parolis nur pri tabelpaĝoj. Kio okazas ene de la indeksoj?

La informoj en indekspaĝoj multe varias depende de la specifa speco de indekso. Kaj eĉ unu speco de indekso havas malsamajn specojn de paĝoj. Ekzemple, B-arbo havas metadatuman paĝon kaj "regulajn" paĝojn.

Tamen, la paĝo kutime havas tabelon da montriloj al la vicoj kaj la vicoj mem (same kiel tabelpaĝo). Krome, ĉe la fino de la paĝo estas spaco por specialaj datumoj.

Vicoj en indeksoj ankaŭ povas havi tre malsamajn strukturojn depende de la speco de indekso. Ekzemple, por B-arbo, la vicoj rilataj al foliaj paĝoj enhavas la indeksan ŝlosilvaloron kaj referencon (ctid) al la responda tabelvico. Ĝenerale, la indekso povas esti strukturita en tute alia maniero.

La plej grava punkto estas, ke ne ekzistas vicversioj en indeksoj de ajna tipo. Nu, aŭ ni povas supozi, ke ĉiu linio estas reprezentita per ekzakte unu versio. Alivorte, ne estas xmin kaj xmax-kampoj en la indeksa vica kaplinio. Ni povas supozi, ke ligiloj de la indekso kondukas al ĉiuj tabelaj versioj de la vicoj - do vi povas eltrovi kian version la transakcio vidos nur rigardante la tabelon. (Kiel ĉiam, ĉi tio ne estas la tuta vero. En iuj kazoj, la videbleco-mapo povas optimumigi la procezon, sed ni rigardos ĉi tion pli detale poste.)

Samtempe, en la indeksa paĝo ni trovas montrilojn al ambaŭ versioj, kaj la nuna kaj la malnova:

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

Virtualaj transakcioj

En praktiko, PostgreSQL uzas optimumigojn, kiuj permesas al ĝi "konservi" transakciajn nombrojn.

Se transakcio nur legas datumojn, ĝi ne efikas sur la videbleco de vicaj versioj. Tial, la serva procezo unue eldonas virtualan xid al la transakcio. La nombro konsistas el proceza ID kaj sinsekvo.

Eldoni ĉi tiun numeron ne postulas sinkronigon inter ĉiuj procezoj kaj tial estas tre rapida. Ni konatiĝos kun alia kialo por uzi virtualajn nombrojn kiam ni parolas pri frosto.

Virtualaj nombroj neniel estas konsiderataj en datumaj momentfotoj.

En malsamaj momentoj, povas bone ekzisti virtualaj transakcioj en la sistemo kun nombroj kiuj jam estis uzitaj, kaj ĉi tio estas normala. Sed tia nombro ne povas esti skribita en datumpaĝojn, ĉar la venontan fojon kiam la paĝo estos alirita, ĝi eble perdos ĉian signifon.

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

Se transakcio komencas ŝanĝi datumojn, ĝi ricevas realan, unikan transakcian numeron.

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

=> COMMIT;

Nestitaj Transakcioj

Konservu punktojn

Difinite en SQL konservi punktojn (konserva punkto), kiuj permesas nuligi parton de transakcio sen tute interrompi ĝin. Sed ĉi tio ne kongruas en la supran diagramon, ĉar la transakcio havas la saman statuson por ĉiuj ĝiaj ŝanĝoj, kaj fizike neniuj datumoj estas retrovigitaj.

Por efektivigi ĉi tiun funkcion, transakcio kun konservpunkto estas dividita en plurajn apartajn nestitaj transakcioj (subtransakcio), kies statuso povas esti administrita aparte.

Nestitaj transakcioj havas sian propran numeron (pli alta ol la nombro de la ĉefa transakcio). La statuso de nestitaj transakcioj estas registrita laŭ la kutima maniero en XACT, sed la fina statuso dependas de la stato de la ĉefa transakcio: se ĝi estas nuligita, tiam ĉiuj nestitaj transakcioj ankaŭ estas nuligitaj.

Informoj pri transakcia nestado estas konservita en dosieroj en la dosierujo PGDATA/pg_subtrans. Dosieroj estas aliritaj tra bufroj en la komuna memoro de la kazo, organizita en laŭ la saman manieron kiel XACT-bufroj.

Ne konfuzu nestitajn transakciojn kun aŭtonomaj transakcioj. Aŭtonomaj transakcioj neniel dependas unu de la alia, sed nestitaj transakcioj jes. Ne estas aŭtonomaj transakcioj en regula PostgreSQL, kaj, eble, plej bone: ili estas bezonataj tre, tre malofte, kaj ilia ĉeesto en aliaj DBMS-oj provokas misuzon, pro kio ĉiuj suferas tiam.

Ni purigu la tabelon, komencu transakcion kaj enigu la vicon:

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

Nun ni metu konservan punkton kaj enigu alian linion.

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

Notu, ke la funkcio txid_current() resendas la ĉefan transakcinumeron, ne la nestitan transakcinumeron.

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

Ni reiru al la konservpunkto kaj enigu la trian linion.

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

En la paĝo ni daŭre vidas la vicon aldonitan de la nuligita nestita transakcio.

Ni riparas la ŝanĝojn.

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

Nun vi povas klare vidi, ke ĉiu nestita transakcio havas sian propran statuson.

Notu, ke nestitaj transakcioj ne povas esti uzataj eksplicite en SQL, tio estas, vi ne povas komenci novan transakcion sen kompletigi la nunan. Ĉi tiu mekanismo estas aktivigita implicite dum uzado de konservpunktoj, same kiel dum pritraktado de PL/pgSQL-esceptoj kaj en kelkaj aliaj, pli ekzotikaj kazoj.

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

Eraroj kaj atomeco de operacioj

Kio okazas se eraro okazas dum operacio? Ekzemple, tiel:

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

Eraro okazis. Nun la transakcio estas konsiderata abortita kaj neniuj operacioj estas permesitaj en ĝi:

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

Kaj eĉ se vi provas fari la ŝanĝojn, PostgreSQL raportos ĉesigon:

=> COMMIT;
ROLLBACK

Kial transakcio ne povas daŭri post fiasko? La fakto estas, ke eraro povus aperi tiel, ke ni akirus aliron al parto de la ŝanĝoj - la atomeco de eĉ ne la transakcio, sed la operatoro estus malobservita. Kiel en nia ekzemplo, kie la operatoro sukcesis ĝisdatigi unu linion antaŭ la eraro:

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

Oni devas diri, ke psql havas reĝimon, kiu ankoraŭ ebligas al la transakcio daŭri post malsukceso kvazaŭ la agoj de la erara operatoro estus reigitaj.

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

Ne estas malfacile konjekti, ke en ĉi tiu reĝimo, psql efektive metas implican konservan punkton antaŭ ĉiu komando, kaj en kazo de malsukceso iniciatas retrovon al ĝi. Ĉi tiu reĝimo ne estas uzata defaŭlte, ĉar agordo de savpunktoj (eĉ sen reveni al ili) implikas signifan ŝarĝon.

Daŭrigo.

fonto: www.habr.com

Aldoni komenton