MVCC-3. Stīgu versijas

Tātad, mēs esam izskatÄ«juÅ”i jautājumus, kas saistÄ«ti ar izolācija, un izdarÄ«ja atkāpÅ”anos par datu organizÄ“Å”ana zemā lÄ«menÄ«. Un visbeidzot mēs nonācām pie interesantākās daļas - stÄ«gu versijām.

Virsraksts

Kā jau teicām, katra rinda vienlaikus var atrasties datu bāzē vairākās versijās. Viena versija ir kaut kā jāatŔķir no otras Å im nolÅ«kam katrai versijai ir divas atzÄ«mes, kas nosaka Ŕīs versijas darbÄ«bas ā€œlaikuā€ (xmin un xmax). Pēdiņās - jo tiek izmantots nevis laiks kā tāds, bet gan Ä«paÅ”s pieaugoÅ”ais skaitÄ«tājs. Un Å”is skaitÄ«tājs ir darÄ«juma numurs.

(Kā parasti, realitāte ir sarežģītāka: skaitÄ«tāja ierobežotās bitu ietilpÄ«bas dēļ transakcijas numurs nevar visu laiku palielināties. Bet mēs Ŕīs detaļas aplÅ«kosim sÄ«kāk, kad nonāksim lÄ«dz iesaldÄ“Å”anai.)

Kad rinda ir izveidota, xmin tiek iestatīts uz darījuma numuru, kas izdeva komandu INSERT, un lauks xmax tiek atstāts tukŔs.

DzÄ“Å”ot rindu, paÅ”reizējās versijas xmax vērtÄ«ba tiek atzÄ«mēta ar tās darÄ«juma numuru, kurā tika veikta DELETE.

Kad rinda tiek modificēta ar komandu UPDATE, faktiski tiek veiktas divas darbÄ«bas: DELETE un INSERT. PaÅ”reizējā rindas versijā xmax ir vienāds ar transakcijas numuru, kas veica ATJAUNINĀJUMU. Pēc tam tiek izveidota jauna tās paÅ”as virknes versija; tā xmin vērtÄ«ba sakrÄ«t ar iepriekŔējās versijas xmax vērtÄ«bu.

Lauki xmin un xmax ir iekļauti rindas versijas galvenē. Papildus Å”iem laukiem galvenē ir arÄ« citi lauki, piemēram:

  • infomask ir bitu sērija, kas nosaka Ŕīs versijas rekvizÄ«tus. Viņu ir diezgan daudz; Mēs pakāpeniski apsvērsim galvenos.
  • ctid ir saite uz nākamo, jaunāku tās paÅ”as rindas versiju. Jaunākajai virknes versijai ctid attiecas uz Å”o versiju. Skaitlim ir forma (x,y), kur x ir lapas numurs, y ir indeksa numurs masÄ«vā.
  • null bitmap ā€” atzÄ«mē tās dotās versijas kolonnas, kurās ir nulles vērtÄ«ba (NULL). NULL nav viena no parastajām datu tipa vērtÄ«bām, tāpēc atribÅ«ts ir jāuzglabā atseviŔķi.

Rezultātā galvene ir diezgan liela - vismaz 23 baiti katrai rindas versijai, un parasti vairāk, pateicoties NULL bitkartei. Ja tabula ir "Å”aura" (tas ir, tajā ir maz kolonnu), pieskaitāmās izmaksas var aizņemt vairāk nekā noderÄ«ga informācija.

ievietot

SÄ«kāk apskatÄ«sim, kā tiek veiktas zema lÄ«meņa virkņu darbÄ«bas, sākot ar ievietoÅ”anu.

Eksperimentiem izveidosim jaunu tabulu ar divām kolonnām un indeksu vienā no tām:

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

Pēc darÄ«juma uzsākÅ”anas ievietosim vienu rindu.

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

Å eit ir mÅ«su paÅ”reizējā darÄ«juma numurs:

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

Apskatīsim lapas saturu. PaplaŔinājuma pageinspect funkcija heap_page_items ļauj iegūt informāciju par rādītājiem un rindu versijām:

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

Ņemiet vērā, ka vārds kaudze programmā PostgreSQL attiecas uz tabulām. Å is ir vēl viens dÄ«vains Ŕī termina lietojums - ir zināms kaudze datu struktÅ«ra, kam nav nekā kopÄ«ga ar tabulu. Å eit Å”is vārds tiek lietots nozÄ«mē ā€œviss ir samests kopāā€, pretstatā sakārtotiem indeksiem.

Funkcija parāda datus ā€œtādos, kādi irā€ grÅ«ti saprotamā formātā. Lai to noskaidrotu, mēs atstāsim tikai daļu informācijas un atÅ”ifrēsim to:

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

Lūk, ko mēs izdarījām:

  • Indeksa numuram ir pievienota nulle, lai tas izskatÄ«tos tāpat kā t_ctid: (lapas numurs, indeksa numurs).
  • AtÅ”ifrēts rādÄ«tāja lp_flags stāvoklis. Å eit tas ir "normāls" - tas nozÄ«mē, ka rādÄ«tājs faktiski attiecas uz virknes versiju. Citas nozÄ«mes apskatÄ«sim vēlāk.
  • No visiem informācijas bitiem lÄ«dz Å”im ir identificēti tikai divi pāri. Biti xmin_committed un xmin_aborted norāda, vai transakcijas numurs xmin ir veikts (pārtraukts). Divi lÄ«dzÄ«gi biti attiecas uz darÄ«juma numuru xmax.

Ko mēs redzam? Ievietojot rindu, tabulas lapā parādÄ«sies indeksa numurs 1, kas norāda uz rindas pirmo un vienÄ«go versiju.

Virknes versijā lauks xmin ir aizpildÄ«ts ar paÅ”reizējā darÄ«juma numuru. DarÄ«jums joprojām ir aktÄ«vs, tāpēc biti xmin_committed un xmin_aborted nav iestatÄ«ti.

Rindas versijas ctid lauks attiecas uz to paÅ”u rindu. Tas nozÄ«mē, ka jaunāka versija nepastāv.

Lauks xmax ir aizpildÄ«ts ar fiktÄ«vu skaitli 0, jo Ŕī rindas versija nav dzēsta un ir aktuāla. DarÄ«jumos Å”im skaitlim netiks pievērsta uzmanÄ«ba, jo ir iestatÄ«ts bits xmax_aborted.

Spersim vēl vienu soli lasāmÄ«bas uzlaboÅ”anai, pievienojot informācijas bitus darÄ«jumu numuriem. Un izveidosim funkciju, jo pieprasÄ«jums mums bÅ«s vajadzÄ«gs vairāk nekā vienu reizi:

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

Šajā formā ir daudz skaidrāk redzams, kas notiek rindas versijas galvenē:

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

LÄ«dzÄ«gu, bet ievērojami mazāk detalizētu informāciju var iegÅ«t no paÅ”as tabulas, izmantojot pseidokolonnas xmin un xmax:

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

Fiksācija

Ja darÄ«jums ir veiksmÄ«gi pabeigts, jums ir jāatceras tā statuss - ņemiet vērā, ka tas ir veikts. Lai to izdarÄ«tu, tiek izmantota struktÅ«ra ar nosaukumu XACT (un pirms 10. versijas to sauca CLOG (commit log), un Å”is nosaukums joprojām ir atrodams dažādās vietās).

XACT nav sistēmas kataloga tabula; tie ir faili direktorijā PGDATA/pg_xact. Katram darÄ«jumam ir divi biti: veikts un pārtraukts ā€” tāpat kā rindas versijas galvenē. Å Ä« informācija ir sadalÄ«ta vairākos failos tikai ērtÄ«bas labad; mēs atgriezÄ«simies pie Ŕīs problēmas, kad apsvērsim iesaldÄ“Å”anu. Un darbs ar Å”iem failiem tiek veikts pa lappusei, tāpat kā ar visiem citiem.

Tātad, kad transakcija tiek veikta XACT, Å”im darÄ«jumam tiek iestatÄ«ts piesaistÄ«tais bits. Un tas ir viss, kas notiek apņemÅ”anās laikā (lai gan mēs vēl nerunājam par iepriekŔējas ierakstÄ«Å”anas žurnālu).

Kad cits darījums piekļūs tabulas lapai, kuru tikko apskatījām, tai būs jāatbild uz vairākiem jautājumiem.

  1. Vai xmin darījums ir pabeigts? Ja nē, izveidotajai virknes versijai nevajadzētu būt redzamai.
    Å Ä« pārbaude tiek veikta, aplÅ«kojot citu struktÅ«ru, kas atrodas instances koplietotajā atmiņā un tiek saukta par ProcArray. Tajā ir visu aktÄ«vo procesu saraksts, un katram ir norādÄ«ts tā paÅ”reizējās (aktÄ«vās) transakcijas numurs.
  2. Ja pabeigts, tad kā - uzņemoties vai atceļot? Ja tas tiek atcelts, tad arī rindas versijai nevajadzētu būt redzamai.
    Tas ir tieÅ”i tas, kam XACT ir paredzēts. Bet, lai gan pēdējās XACT lapas tiek glabātas RAM buferos, joprojām ir dārgi pārbaudÄ«t XACT katru reizi. Tāpēc, tiklÄ«dz ir noteikts darÄ«juma statuss, tas tiek ierakstÄ«ts virknes versijas bitos xmin_committed un xmin_aborted. Ja ir iestatÄ«ts kāds no Å”iem bitiem, transakcijas stāvoklis xmin tiek uzskatÄ«ts par zināmu un nākamajam darÄ«jumam nebÅ«s jāpiekļūst XACT.

Kāpēc Å”ie biti netiek iestatÄ«ti ar transakciju, kas veic ievietoÅ”anu? Kad notiek ievietoÅ”ana, darÄ«jums vēl nezina, vai tas izdosies. Un apņemÅ”anās brÄ«dÄ« vairs nav skaidrs, kurās rindās kurā lapās tika mainÄ«tas. Šādu lapu var bÅ«t daudz, un to iegaumÄ“Å”ana ir neizdevÄ«ga. Turklāt dažas lapas var izlikt no bufera keÅ”atmiņas uz disku; to lasÄ«Å”ana vēlreiz, lai mainÄ«tu bitus, ievērojami palēninātu saistÄ«bu izpildi.

IetaupÄ«juma mÄ«nuss ir tāds, ka pēc izmaiņām jebkurÅ” darÄ«jums (pat tāds, kas veic vienkārÅ”u lasÄ«Å”anu ā€“ SELECT) var sākt mainÄ«t datu lapas bufera keÅ”atmiņā.

Tātad, labosim izmaiņas.

=> COMMIT;

Lapā nekas nav mainījies (taču mēs zinām, ka darījuma statuss jau ir reģistrēts XACT):

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

Tagad darījumam, kas vispirms piekļūst lapai, būs jānosaka xmin darījuma statuss un jāieraksta tas informācijas bitos:

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

PārcelŔanās

Kad rinda tiek dzēsta, paÅ”reizējās versijas laukā xmax tiek ierakstÄ«ts paÅ”reizējās dzÄ“Å”anas transakcijas numurs un tiek notÄ«rÄ«ts bits xmax_aborted.

Ņemiet vērā, ka iestatÄ«tā xmax vērtÄ«ba, kas atbilst aktÄ«vajam darÄ«jumam, darbojas kā rindas bloķētājs. Ja cits darÄ«jums vēlas atjaunināt vai dzēst Å”o rindu, tas bÅ«s spiests gaidÄ«t, lÄ«dz transakcija xmax tiks pabeigta. Vairāk par bloÄ·Ä“Å”anu runāsim vēlāk. Pagaidām mēs tikai atzÄ«mējam, ka rindu slēdzeņu skaits ir neierobežots. Tie neaizņem vietu RAM, un sistēmas veiktspēja necieÅ” no to skaita. Tiesa, ā€œilgiemā€ darÄ«jumiem ir arÄ« citi trÅ«kumi, bet par to vēlāk.

Izdzēsīsim rindu.

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

Mēs redzam, ka darījuma numurs ir ierakstīts laukā xmax, bet informācijas biti nav iestatīti:

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

AtcelŔana

Izmaiņu pārtraukÅ”ana darbojas lÄ«dzÄ«gi kā apņemÅ”anās, tikai XACT transakcijai tiek iestatÄ«ts pārtrauktais bits. AtcelÅ”ana ir tikpat ātra kā apņemÅ”anās. Lai gan komanda tiek saukta ROLLBACK, izmaiņas netiek atgrieztas: viss, ko darÄ«jumam izdevās mainÄ«t datu lapās, paliek nemainÄ«gs.

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

Kad lapa tiek atvērta, statuss tiks pārbaudīts, un mājienu bitam xmax_aborted tiks iestatīta rindas versija. Pats xmax numurs paliek lapā, bet neviens uz to neskatīsies.

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

Modernizēt

Atjauninājums darbojas tā, it kā vispirms tiktu izdzēsta paÅ”reizējā rindas versija un pēc tam ievietota jauna.

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

Vaicājums rada vienu rindiņu (jauna versija):

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

Bet lapā mēs redzam abas versijas:

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

Izdzēstā versija ir atzÄ«mēta ar paÅ”reizējā darÄ«juma numuru laukā xmax. Turklāt Ŕī vērtÄ«ba tiek rakstÄ«ta virs vecā, jo iepriekŔējais darÄ«jums tika atcelts. Un bits xmax_aborted tiek notÄ«rÄ«ts, jo paÅ”reizējā darÄ«juma statuss vēl nav zināms.

Pirmā rindas versija tagad attiecas uz otro (lauks t_ctid) kā jaunāko versiju.

Otrais rādītājs parādās rādītāja lapā, un otrā rinda norāda uz otro versiju tabulas lapā.

Tāpat kā dzÄ“Å”anas gadÄ«jumā, xmax vērtÄ«ba rindas pirmajā versijā norāda, ka rinda ir bloķēta.

Nu, pabeigsim darījumu.

=> COMMIT;

Indeksi

Līdz Ŕim esam runājuŔi tikai par tabulu lapām. Kas notiek indeksos?

Informācija rādÄ«tāja lapās ievērojami atŔķiras atkarÄ«bā no konkrētā rādÄ«tāja veida. Un pat viena veida indeksam ir dažāda veida lapas. Piemēram, B kokam ir metadatu lapa un ā€œparastāsā€ lapas.

Tomēr lapā parasti ir masÄ«vs norādes uz rindām un paŔām rindām (tāpat kā tabulas lapā). Turklāt lapas beigās ir vieta Ä«paÅ”iem datiem.

ArÄ« indeksu rindām var bÅ«t ļoti atŔķirÄ«ga struktÅ«ra atkarÄ«bā no indeksa veida. Piemēram, B kokam rindas, kas saistÄ«tas ar lapu lapām, satur indeksÄ“Å”anas atslēgas vērtÄ«bu un atsauci (ctid) uz atbilstoÅ”o tabulas rindu. Kopumā indeksu var strukturēt pavisam citādi.

VissvarÄ«gākais ir tas, ka neviena veida indeksos nav rindu versiju. Nu, vai arÄ« mēs varam pieņemt, ka katra rinda ir attēlota tieÅ”i ar vienu versiju. Citiem vārdiem sakot, indeksa rindas galvenē nav lauku xmin un xmax. Mēs varam pieņemt, ka saites no indeksa ved uz visām rindu tabulu versijām - tāpēc jÅ«s varat noskaidrot, kuru versiju darÄ«jums redzēs, tikai apskatot tabulu. (Kā vienmēr, tā nav visa patiesÄ«ba. Dažos gadÄ«jumos redzamÄ«bas karte var optimizēt procesu, taču mēs to aplÅ«kosim vēlāk.)

Tajā paŔā laikā rādÄ«tāja lapā mēs atrodam norādes uz abām versijām, gan paÅ”reizējo, gan veco:

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

Virtuālie darījumi

Praksē PostgreSQL izmanto optimizācijas, kas ļauj ā€œsaglabātā€ darÄ«jumu numurus.

Ja darÄ«jums tikai nolasa datus, tas neietekmē rindu versiju redzamÄ«bu. Tāpēc apkalpoÅ”anas process vispirms darÄ«jumam izsniedz virtuālo xid. Numurs sastāv no procesa ID un kārtas numura.

Å Ä« numura izsniegÅ”anai nav nepiecieÅ”ama sinhronizācija starp visiem procesiem, un tāpēc tas ir ļoti ātrs. Mēs iepazÄ«simies ar vēl vienu virtuālo numuru izmantoÅ”anas iemeslu, runājot par iesaldÄ“Å”anu.

Datu momentuzņēmumos virtuālie skaitļi nekādā veidā netiek ņemti vērā.

Dažādos brīžos sistēmā var bÅ«t virtuāli darÄ«jumi ar numuriem, kas jau ir izmantoti, un tas ir normāli. Bet Ŕādu numuru nevar ierakstÄ«t datu lapās, jo nākamajā reizē, kad lapa tiek atvērta, tas var zaudēt visu nozÄ«mi.

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

Ja darījums sāk mainīt datus, tam tiek pieŔķirts reāls unikāls darījuma numurs.

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

=> COMMIT;

Ligzdotie darījumi

Saglabājiet punktus

Definēts SQL saglabā punktus (savepoint), kas ļauj atcelt daļu darÄ«juma, nepārtraucot to pilnÄ«bā. Bet tas neiekļaujas iepriekÅ” minētajā diagrammā, jo darÄ«jumam ir vienāds statuss visām tā izmaiņām un fiziski dati netiek atgriezti.

Lai ieviestu Ŕo funkcionalitāti, darījums ar saglabāŔanas punktu tiek sadalīts vairākos atseviŔķos ligzdoti darījumi (apakŔdarījums), kura statusu var pārvaldīt atseviŔķi.

Ligzdotajiem darījumiem ir savs numurs (lielāks nekā galvenā darījuma numurs). Ligzdoto transakciju statuss tiek fiksēts parastajā veidā XACT, bet gala statuss ir atkarīgs no galvenā darījuma statusa: ja tas tiek atcelts, tad tiek atcelti arī visi ligzdotie darījumi.

Informācija par darÄ«jumu ligzdoÅ”anu tiek glabāta failos direktorijā PGDATA/pg_subtrans. Failiem var piekļūt, izmantojot gadÄ«juma koplietotās atmiņas buferus, kas sakārtoti tāpat kā XACT buferi.

Nejauciet ligzdotos darÄ«jumus ar autonomiem darÄ«jumiem. Autonomie darÄ«jumi nekādā veidā nav atkarÄ«gi viens no otra, bet ligzdotie darÄ«jumi ir atkarÄ«gi. Parastā PostgreSQL nav autonomu darÄ«jumu, un, iespējams, labākajā gadÄ«jumā: tie ir nepiecieÅ”ami ļoti, ļoti reti, un to klātbÅ«tne citās DBVS provocē ļaunprātÄ«gu izmantoÅ”anu, no kuras visi cieÅ”.

Notīrīsim tabulu, sāksim darījumu un ievietosim rindu:

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

Tagad ievietosim saglabāŔanas punktu un ievietosim citu rindiņu.

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

Ņemiet vērā, ka funkcija txid_current() atgriež galvenā darījuma numuru, nevis ligzdotā darījuma numuru.

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

AtgriezÄ«simies lÄ«dz saglabāŔanas punktam un ievietosim treÅ”o rindiņu.

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

Lapā mēs turpinām redzēt rindu, ko pievienoja atceltais ligzdotais darījums.

Mēs labojam izmaiņas.

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

Tagad jūs varat skaidri redzēt, ka katram ligzdotajam darījumam ir savs statuss.

Ņemiet vērā, ka ligzdotas transakcijas nevar izmantot tieÅ”i SQL, tas ir, jÅ«s nevarat sākt jaunu transakciju, nepabeidzot paÅ”reizējo. Å is mehānisms tiek aktivizēts netieÅ”i, izmantojot saglabāŔanas punktus, kā arÄ« apstrādājot PL/pgSQL izņēmumus un vairākos citos, eksotiskākos gadÄ«jumos.

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

Kļūdas un darbību atomitāte

Kas notiek, ja darbÄ«bas laikā rodas kļūda? Piemēram, Ŕādi:

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

Radās kļūda. Tagad darījums tiek uzskatīts par pārtrauktu, un tajā nav atļautas nekādas darbības:

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

Un pat tad, ja mēģināt veikt izmaiņas, PostgreSQL ziņos par pārtraukÅ”anu:

=> COMMIT;
ROLLBACK

Kāpēc darījums nevar turpināties pēc neveiksmes? Fakts ir tāds, ka kļūda varētu rasties tādā veidā, ka mēs piekļūtu daļai izmaiņu - tiktu pārkāpts pat ne darījuma atomitāte, bet operators. Tāpat kā mūsu piemērā, kur operatoram izdevās atjaunināt vienu rindiņu pirms kļūdas:

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

Jāteic, ka psql ir režīms, kas joprojām ļauj darījumu turpināt pēc neveiksmes, it kā kļūdainā operatora darbības tiktu atsauktas.

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

Nav grÅ«ti uzminēt, ka Å”ajā režīmā psql faktiski ievieto netieÅ”u saglabāŔanas punktu pirms katras komandas un kļūmes gadÄ«jumā sāk tās atcelÅ”anu. Å is režīms netiek izmantots pēc noklusējuma, jo saglabāŔanas punktu iestatÄ«Å”ana (pat neatgriežoties pie tiem) ir saistÄ«ta ar ievērojamām izmaksām.

Turpinājums.

Avots: www.habr.com

Pievieno komentāru