MVCC-3. String ferzjes

Dat, wy hawwe beskôge problemen yn ferbân mei isolaasje, en makke in retreat oer organisearje gegevens op in leech nivo. En as lêste kamen wy by it meast nijsgjirrige diel - de snaarferzjes.

Header

Lykas wy al sein hawwe, kin elke rige tagelyk bestean yn ferskate ferzjes yn 'e databank. Ien ferzje moat op ien of oare manier ûnderskiede wurde fan in oare. Foar dit doel hat elke ferzje twa tekens dy't de "tiid" fan aksje fan dizze ferzje bepale (xmin en xmax). Yn quotes - om't it net tiid as sadanich brûkt wurdt, mar in spesjale tanimmende teller. En dizze teller is it transaksjenûmer.

(Lykas gewoanlik is de realiteit komplisearre: it transaksjenûmer kin net hieltyd ferheegje troch de beheinde bitkapasiteit fan 'e teller. Mar wy sille dizze details yn detail besjen as wy by it befriezen komme.)

As in rige wurdt oanmakke, wurdt xmin ynsteld op it transaksjenûmer dat it kommando INSERT útjûn, en xmax wurdt leech litten.

As in rige wiske wurdt, wurdt de xmax-wearde fan 'e aktuele ferzje markearre mei it nûmer fan 'e transaksje dy't de DELETE hat útfierd.

As in rige wizige wurdt troch in UPDATE-kommando, wurde twa operaasjes eins útfierd: DELETE en INSERT. De hjoeddeistige ferzje fan 'e rige stelt xmax gelyk oan it nûmer fan 'e transaksje dy't de UPDATE útfierde. In nije ferzje fan deselde tekenrige wurdt dan makke; syn xmin-wearde komt oerien mei de xmax-wearde fan 'e foarige ferzje.

De xmin- en xmax-fjilden binne opnommen yn 'e koptekst fan' e rigeferzje. Neist dizze fjilden befettet de koptekst oaren, bygelyks:

  • infomask is in searje bits dy't de eigenskippen fan dizze ferzje definiearje. Der binne nochal in protte fan harren; Wy sille stadichoan de wichtichste beskôgje.
  • ctid is in keppeling nei de folgjende, nijere ferzje fan deselde rigel. Foar de nijste, meast aktuele ferzje fan in tekenrige, ferwiist de ctid nei dizze ferzje sels. It nûmer hat de foarm (x,y), wêrby't x it sidenûmer is, y it yndeksnûmer yn 'e array is.
  • null bitmap - Markearret dy kolommen fan in opjûne ferzje dy't in nulwearde (NULL) befetsje. NULL is net ien fan 'e normale gegevenstypewearden, dus it attribút moat apart wurde opslein.

As resultaat is de koptekst frij grut - op syn minst 23 bytes foar elke ferzje fan 'e line, en meastentiids mear troch de NULL-bitmap. As de tabel "smel" is (dat is, befettet in pear kolommen), kin de overhead mear opnimme as de nuttige ynformaasje.

ynfoegje

Litte wy in tichterby besjen hoe't stringoperaasjes op leech nivo wurde útfierd, begjinnend mei ynfoegje.

Litte wy foar eksperiminten in nije tabel meitsje mei twa kolommen en in yndeks op ien fan har:

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

Litte wy ien rige ynfoegje nei it begjinnen fan in transaksje.

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

Hjir is ús hjoeddeistige transaksjenûmer:

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

Litte wy nei de ynhâld fan 'e side sjen. De funksje heap_page_items fan 'e pageinspect-útwreiding lit jo ynformaasje krije oer oanwizers en rigeferzjes:

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

Tink derom dat it wurd heap yn PostgreSQL ferwiist nei tabellen. Dit is in oar nuver gebrûk fan 'e term - in heap is bekend data struktuer, dat hat neat gemien mei de tafel. Hjir wurdt it wurd brûkt yn 'e betsjutting fan "alles wurdt byinoar smiten," yn tsjinstelling ta oardere yndeksen.

De funksje toant gegevens "as is", yn in opmaak dat is lestich te begripen. Om it út te finen litte wy mar in diel fan 'e ynformaasje litte en it ûntsiferje:

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

Hjir is wat wy dien hawwe:

  • In nul tafoege oan it yndeksnûmer om it itselde te meitsjen as t_ctid: (sidenûmer, yndeksnûmer).
  • Ûntsifere de steat fan de lp_flags oanwizer. Hjir is it "normaal" - dit betsjut dat de oanwizer eins ferwiist nei de ferzje fan 'e tekenrige. Wy sille letter nei oare betsjuttings sjen.
  • Fan alle ynformaasjebits binne oant no ta mar twa pearen identifisearre. De bits xmin_committed en xmin_aborted jouwe oan oft transaksjenûmer xmin is ynset (ôfbrutsen). Twa ferlykbere bits ferwize nei transaksjenûmer xmax.

Wat sjogge wy? As jo ​​​​in rige ynfoegje, sil in yndeksnûmer 1 ferskine yn 'e tabelside, dy't wiist nei de earste en ienige ferzje fan' e rige.

Yn 'e stringferzje is it xmin-fjild fol mei it hjoeddeistige transaksjenûmer. De transaksje is noch aktyf, sadat sawol de xmin_committed as xmin_aborted bits net ynsteld binne.

De rige ferzje ctid fjild ferwiist nei deselde rige. Dit betsjut dat in nijere ferzje net bestiet.

It xmax-fjild is fol mei in dummynûmer 0, om't dizze ferzje fan 'e rige net wiske is en aktueel is. Transaksjes sille net betelje omtinken oan dit nûmer omdat de xmax_aborted bit is ynsteld.

Litte wy noch ien stap nimme om de lêsberens te ferbetterjen troch ynformaasjebits ta te foegjen oan transaksjenûmers. En lit ús in funksje oanmeitsje, om't wy it fersyk mear as ien kear nedich binne:

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

Yn dizze foarm is it folle dúdliker wat der bart yn 'e koptekst fan' e rigeferzje:

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

Fergelykbere, mar signifikant minder detaillearre, ynformaasje kin wurde krigen fan 'e tabel sels, mei pseudo-kolommen xmin en xmax:

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

Fixaasje

As in transaksje mei súkses foltôge is, moatte jo har status betinke - tink derom dat it ynset is. Om dit te dwaan, wurdt in struktuer neamd XACT brûkt (en foar ferzje 10 waard it neamd CLOG (commit log) en dizze namme is noch te finen op ferskate plakken).

XACT is gjin systeem katalogus tabel; dit binne de bestannen yn de map PGDATA/pg_xact. Se hawwe twa bits foar elke transaksje: ynset en ôfbrutsen - krekt as yn 'e koptekst fan' e rigeferzje. Dizze ynformaasje is allinich foar gemak ferdield yn ferskate bestannen; wy sille weromkomme op dit probleem as wy beferzen beskôgje. En wurk mei dizze bestannen wurdt side foar side útfierd, lykas mei alle oaren.

Dus, as in transaksje wurdt ynset yn XACT, wurdt it ynsette bit ynsteld foar dizze transaksje. En dit is alles wat bart by it begean (hoewol't wy it noch net hawwe oer it logboek foar foaropname).

As in oare transaksje tagong hat ta de tabelside wêr't wy krekt nei hawwe sjoen, sil it ferskate fragen moatte beantwurdzje.

  1. Is de xmin-transaksje foltôge? As net, dan moat de oanmakke ferzje fan 'e tekenrige net sichtber wêze.
    Dizze kontrôle wurdt útfierd troch te sjen nei in oare struktuer, dy't leit yn it dielde ûnthâld fan 'e eksimplaar en hjit ProcArray. It befettet in list fan alle aktive prosessen, en foar elk wurdt it nûmer fan har hjoeddeistige (aktive) transaksje oanjûn.
  2. As foltôge, hoe dan - troch te begean of te annulearjen? As annulearre, dan soe de rige ferzje ek net sichtber wêze moatte.
    Dit is krekt wêr't XACT foar is. Mar, hoewol de lêste siden fan XACT wurde opslein yn buffers yn RAM, is it noch altyd djoer om XACT elke kear te kontrolearjen. Dêrom, as de transaksjestatus ienris bepaald is, wurdt it skreaun nei de xmin_committed en xmin_aborted bits fan 'e stringferzje. As ien fan dizze bits ynsteld is, dan wurdt de tastân fan transaksje xmin as bekend beskôge en sil de folgjende transaksje gjin tagong krije ta XACT.

Wêrom wurde dizze bits net ynsteld troch de transaksje sels dy't it ynfoegje docht? Wannear't in ynfoegje bart, wit de transaksje noch net oft it sil slagje. En op it momint fan commit is it net mear dúdlik hokker rigels wêryn't siden feroare binne. D'r kinne in protte fan sokke siden wêze, en it ûnthâlden fan har is net rendabel. Dêrnjonken kinne guon siden út 'e buffer-cache nei skiif ferwidere wurde; se opnij lêze om de bits te feroarjen soe de commit signifikant fertrage.

It neidiel fan 'e besparring is dat nei feroaringen elke transaksje (sels ien dy't in ienfâldige lêzing útfiert - SELECT) kin begjinne om gegevenssiden yn' e buffer-cache te feroarjen.

Dat, lit ús de feroaring reparearje.

=> COMMIT;

Der is neat feroare op 'e side (mar wy witte dat de transaksjestatus al is opnommen yn XACT):

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

No sil de transaksje dy't earst tagong hat ta de side de xmin-transaksjestatus moatte bepale en it skriuwe nei de ynformaasjebits:

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

Ferwiderje

As in rige is wiske, wurdt it nûmer fan 'e aktuele wiskjende transaksje skreaun nei it xmax-fjild fan 'e aktuele ferzje, en it xmax_aborted-bit wurdt wiske.

Tink derom dat de ynstelde wearde fan xmax oerienkommende mei de aktive transaksje fungearret as in rige slot. As in oare transaksje wol bywurkje of wiskje dizze rige, it sil wurde twongen om te wachtsjen foar transaksje xmax te foltôgjen. Wy sille letter mear prate oer blokkearjen. Foar no konstatearje wy gewoan dat it oantal rigelsloten ûnbeheind is. Se nimme gjin romte yn RAM en systeemprestaasjes hawwe gjin lêst fan har nûmer. Wier, "lange" transaksjes hawwe oare neidielen, mar mear oer dat letter.

Litte wy de line wiskje.

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

Wy sjogge dat it transaksjenûmer yn it xmax-fjild skreaun is, mar de ynformaasjebits binne net ynsteld:

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

cancellation

It ôfbrekken fan wizigingen wurket fergelykber mei it begean, allinich yn XACT wurdt it ôfbrutsen bit ynsteld foar de transaksje. Undoen is sa rap as commit. Hoewol it kommando ROLLBACK hjit, wurde wizigingen net weromdraaid: alles wat de transaksje slagge om te feroarjen yn 'e gegevenssiden bliuwt net feroare.

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

As de side tagong wurdt, sil de status kontrolearre wurde en sil it xmax_aborted hintbit ynsteld wurde op de rigeferzje. It xmax-nûmer sels bliuwt op 'e side, mar gjinien sil der nei sjen.

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

Update

De fernijing wurket as soe it earst de aktuele ferzje fan 'e rige wiske en dêrnei in nije ynfoege.

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

De query produseart ien rigel (nije ferzje):

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

Mar op 'e side sjogge wy beide ferzjes:

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

De wiske ferzje is markearre mei it aktuele transaksjenûmer yn it xmax-fjild. Boppedat, dizze wearde wurdt skreaun oer de âlde, sûnt de foarige transaksje waard annulearre. En de xmax_aborted bit wurdt wiske omdat de status fan de hjoeddeiske transaksje is noch net bekend.

De earste ferzje fan 'e rigel ferwiist no nei it twadde (t_ctid-fjild) as de nijere.

In twadde yndeks ferskynt yn 'e yndeksside en in twadde rige ferwiist nei de twadde ferzje yn' e tabelside.

Krekt as by wiskjen is de xmax-wearde yn 'e earste ferzje fan' e rige in yndikaasje dat de rige is beskoattele.

No, litte wy de transaksje foltôgje.

=> COMMIT;

Yndeksen

Oant no ta hawwe wy it allinnich oer tabelsiden. Wat bart der binnen de yndeksen?

De ynformaasje yn yndekssiden ferskilt sterk ôfhinklik fan it spesifike type yndeks. En sels ien type yndeks hat ferskate soarten siden. Bygelyks, in B-beam hat in metadata-side en "gewoane" siden.

De side hat lykwols normaal in array fan oanwizers nei de rigen en de rigen sels (krekt as in tabelside). Dêrneist is der oan 'e ein fan 'e side romte foar spesjale gegevens.

Rigen yn yndeksen kinne ek hiel ferskillende struktueren hawwe ôfhinklik fan it type yndeks. Bygelyks, foar in B-beam befetsje de rigen yn ferbân mei blêdsiden de yndeksearjende kaaiwearde en in ferwizing (ctid) nei de oerienkommende tabelrige. Yn 't algemien kin de yndeks op in folslein oare manier strukturearre wurde.

It wichtichste punt is dat der gjin rige ferzjes yn yndeksen fan hokker type. No, of wy kinne oannimme dat elke rigel wurdt fertsjintwurdige troch krekt ien ferzje. Mei oare wurden, d'r binne gjin xmin- en xmax-fjilden yn 'e koptekst fan' e yndeksrige. Wy kinne oannimme dat keppelings út de yndeks liede ta alle tabel ferzjes fan de rigen - dus jo kinne útfine hokker ferzje de transaksje sil sjen allinne troch te sjen op 'e tabel. (Lykas altyd is dit net de hiele wierheid. Yn guon gefallen kin de sichtberenskaart it proses optimalisearje, mar wy sille dit letter yn mear detail besjen.)

Tagelyk fine wy ​​op 'e yndeksside oanwizers nei beide ferzjes, sawol de hjoeddeiske as de âlde:

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

Firtuele transaksjes

Yn 'e praktyk brûkt PostgreSQL optimisaasjes dy't it mooglik meitsje om transaksjenûmers te "bewarje".

As in transaksje allinnich lêst gegevens, it hat gjin effekt op de sichtberens fan rige ferzjes. Dêrom jout it tsjinstproses earst in firtuele xid út foar de transaksje. It nûmer bestiet út in proses ID en in folchoarder nûmer.

It útjaan fan dit nûmer fereasket gjin syngronisaasje tusken alle prosessen en is dêrom heul fluch. Wy sille yn 'e kunde komme mei in oare reden foar it brûken fan firtuele nûmers as wy prate oer freezing.

Firtuele nûmers wurde op gjin inkelde manier yn rekken brocht yn snapshots fan gegevens.

Op ferskate punten yn 'e tiid kinne d'r miskien firtuele transaksjes yn it systeem wêze mei nûmers dy't al brûkt binne, en dit is normaal. Mar sa'n nûmer kin net yn gegevenssiden skreaun wurde, want de folgjende kear dat de side tagong wurdt kin it alle betsjutting ferlieze.

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

As in transaksje begjint gegevens te feroarjen, wurdt it in echte, unyk transaksjenûmer jûn.

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

=> COMMIT;

Nested Transaksjes

Bewarje punten

Definearre yn SQL bewarje punten (savepoint), wêrtroch jo in diel fan in transaksje kinne annulearje sûnder it folslein te ûnderbrekken. Mar dit past net yn it boppesteande diagram, om't de transaksje deselde status hat foar al har feroaringen, en fysyk gjin gegevens wurde weromrôle.

Om dizze funksjonaliteit út te fieren, wurdt in transaksje mei in opslachpunt opdield yn ferskate aparte nêst transaksjes (subtransaksje), wêrfan de status apart kin wurde beheard.

Neste transaksjes hawwe har eigen nûmer (heger as it nûmer fan 'e haadtransaksje). De status fan nestele transaksjes wurdt op 'e gewoane manier opnommen yn XACT, mar de definitive status hinget ôf fan' e status fan 'e haadtransaksje: as it annulearre is, dan wurde alle nestede transaksjes ek annulearre.

Ynformaasje oer transaksje nêst wurdt opslein yn triemmen yn de PGDATA / pg_subtrans triemtafel. Triemmen wurde tagong fia buffers yn it dielde ûnthâld fan 'e eksimplaar, organisearre op deselde manier as XACT-buffers.

Ferwarre geneste transaksjes net mei autonome transaksjes. Autonome transaksjes binne op gjin inkelde manier ôfhinklik fan elkoar, mar geneste transaksjes dogge dat. D'r binne gjin autonome transaksjes yn reguliere PostgreSQL, en, miskien, foar it bêste: se binne heul, heul selden nedich, en har oanwêzigens yn oare DBMS's provosearret misbrûk, wêrfan elkenien dan lijt.

Litte wy de tabel wiskje, in transaksje begjinne en de rige ynfoegje:

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

Litte wy no in opslachpunt sette en in oare rigel ynfoegje.

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

Tink derom dat de funksje txid_current () it haadtransaksjenûmer weromjout, net it geneste transaksjenûmer.

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

Litte wy weromgean nei it opslachpunt en de tredde rigel ynfoegje.

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

Op 'e side bliuwe wy de rige te sjen tafoege troch de annulearre nestele transaksje.

Wy reparearje de wizigingen.

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

No kinne jo dúdlik sjen dat elke geneste transaksje in eigen status hat.

Tink derom dat geneste transaksjes net eksplisyt brûkt wurde kinne yn SQL, dat is, jo kinne gjin nije transaksje begjinne sûnder de aktuele te foltôgjen. Dit meganisme wurdt ymplisyt aktivearre by it brûken fan savepoints, lykas by it behanneljen fan PL/pgSQL-útsûnderings en yn in oantal oare, mear eksoatyske gefallen.

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

Flaters en atomiteit fan operaasjes

Wat bart der as in flater optreedt by it útfieren fan in operaasje? Bygelyks, lykas dit:

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

Der is in flater bard. No wurdt de transaksje beskôge as ôfbrutsen en gjin operaasjes binne tastien yn it:

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

En sels as jo besykje de wizigingen te dwaan, sil PostgreSQL in ôfbrekke rapportearje:

=> COMMIT;
ROLLBACK

Wêrom kin in transaksje net trochgean nei in mislearring? It feit is dat in flater koe ûntstean op sa'n wize dat wy soenen krije tagong ta in part fan de feroarings - de atomicity fan net iens de transaksje, mar de operator soe wurde skeind. Lykas yn ús foarbyld, wêr't de operator ien rigel foar de flater bywurke koe:

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

It moat sein wurde dat psql in modus hat dy't de transaksje noch kin trochgean nei in mislearring as soe de aksjes fan 'e ferkearde operator weromdraaid wurde.

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

It is net dreech om te rieden dat yn dizze modus psql eins in ymplisyt opslachpunt pleatst foar elk kommando, en yn gefal fan mislearring inisjearret in rollback nei it. Dizze modus wurdt net standert brûkt, om't it ynstellen fan opslachpunten (sels sûnder werom te rôljen) in signifikante overhead meibringt.

Fuortsetting.

Boarne: www.habr.com

Add a comment