MVCC-3. Radversioner

SĂ„, vi har diskuterat frĂ„gor relaterade till isolering, och gjort en avstickare om dataorganisation pĂ„ lĂ„g nivĂ„. Och till sist har vi kommit till det mest intressanta – versionerna av rader.

Rubrik

Som vi redan har nĂ€mnt kan varje rad samtidigt förekomma i databasen i flera versioner. En version mĂ„ste sĂ€rskiljas frĂ„n en annan. För detta Ă€ndamĂ„l har varje version tvĂ„ mĂ€rkningar som definierar "tiden" för denna versions giltighet (xmin och xmax). I citat – eftersom det inte anvĂ€nds tid i egentlig mening, utan en speciell vĂ€xande rĂ€knare. Och denna rĂ€knare Ă€r transaktionsnumret.

(Som vanligt Àr det i verkligheten allt mer komplicerat: transaktionsnumret kan inte hela tiden öka pÄ grund av den begrÀnsade datalÀngden för rÀknaren. Men dessa detaljer kommer vi att titta nÀrmare pÄ nÀr vi kommer till frysningsaspekten.)

NÀr en rad skapas sÀtts vÀrdet för xmin till transaktionsnumret som utförde INSERT-kommandot, medan xmax inte fylls i.

NÀr en rad tas bort mÀrks vÀrdet för xmax i den aktuella versionen med transaktionsnumret som utförde DELETE.

NÀr en rad Àndras med UPDATE-kommandot utförs faktiskt tvÄ operationer: DELETE och INSERT. I den aktuella versionen av raden sÀtts xmax till transaktionsnumret som utförde UPDATE. Sedan skapas en ny version av samma rad; vÀrdet för xmin överensstÀmmer med vÀrdet för xmax i den föregÄende versionen.

FÀlten xmin och xmax ingÄr i radens versionshuvud. Förutom dessa fÀlt innehÄller huvudet Àven andra, till exempel:

  • infomask – en rad bitar som definierar egenskaperna för denna version. Det finns ganska mĂ„nga av dem; de viktigaste kommer vi att gĂ„ igenom steg för steg.
  • ctid – en referens till nĂ€sta, nyare version av samma rad. I den senaste, aktuella versionen pekar ctid pĂ„ just denna version. Numret har formatet (x,y), dĂ€r x Ă€r sidnumret och y Ă€r ordningsnumret för pekaren i arrayen.
  • en bitmask för osĂ€kra vĂ€rden – markerar de kolumner i denna version som innehĂ„ller ett osĂ€kert vĂ€rde (NULL). NULL Ă€r inte ett av de vanliga vĂ€rdena för datatyperna, sĂ„ denna indikator mĂ„ste lagras separat.

Som ett resultat blir huvudet ganska stort – minst 23 byte per radversion, och oftast mer pĂ„ grund av NULL-bitmasken. Om tabellen Ă€r "smal" (det vill sĂ€ga innehĂ„ller fĂ„ kolumner) kan overheadkostnaderna vara större Ă€n nyttoinformationen.

Infoga

LÄt oss titta nÀrmare pÄ hur operationer med strÀngar utförs pÄ lÄg nivÄ och börja med infogningen.

För experimentet skapar vi en ny tabell med tvÄ kolumner och ett index pÄ en av dem:

=> SKAPA TABELL t(
  id serial,
  s text
);
=> SKAPA INDEX PÅ t(s);

Vi infogar en rad, först börjar vi med en transaktion.

=> BÖRJA;
=> INFOGA IN I t(s) VÄRDEN ('FOO');

HÀr Àr numret pÄ vÄr nuvarande transaktion:

=> VÄLJ txid_current();
 txid_current 
--------------
         3664
(1 rad)

LÄt oss titta pÄ innehÄllet pÄ sidan. Funktionen heap_page_items frÄn tillÀgget pageinspect gör det möjligt att fÄ information om pekare och versioner av rader:

=> VÄLJ * FRÅN heap_page_items(get_raw_page('t',0)) gx
-[ POST 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

Notera att ordet heap (hög) i PostgreSQL avser tabeller. Detta Ă€r ytterligare en underlig anvĂ€ndning av termen — en hög Ă€r en kĂ€nd datastruktur, som inte har nĂ„got gemensamt med en tabell. HĂ€r anvĂ€nds ordet i betydelsen 'allt Ă€r hopblandat', till skillnad frĂ„n ordnade index.

Funktionen visar data "som de Àr", i ett format som Àr svÄrt att förstÄ. För att reda ut detta kommer vi att lÀmna kvar endast en del av informationen och avkoda den:

=> VÄLJ '(0,'||lp||')' SOM ctid,
       FALL lp_flags
         NÄR 0 DÅ 'anvĂ€nds ej'
         NÄR 1 DÅ 'normal'
         NÄR 2 DÅ 'omdirigera till '||lp_off
         NÄR 3 DÅ 'död'
       SLUT SOM tillstÄnd,
       t_xmin som xmin,
       t_xmax som xmax,
       (t_infomask & 256) > 0  SOM xmin_commited,
       (t_infomask & 512) > 0  SOM xmin_aborted,
       (t_infomask & 1024) > 0 SOM xmax_commited,
       (t_infomask & 2048) > 0 SOM xmax_aborted,
       t_ctid
FRÅN heap_page_items(get_raw_page('t',0)) gx
-[ POST 1 ]-+-------
ctid          | (0,1)
tillstÄnd     | normal
xmin          | 3664
xmax          | 0
xmin_commited | f
xmin_aborted  | f
xmax_commited | f
xmax_aborted  | t
t_ctid        | (0,1)

Det hÀr Àr vad vi har gjort:

  • Vi lade till en nolla till pekarens nummer för att fĂ„ det i samma form som t_ctid: (sidanummer, pekarnummer).
  • Vi avkodade tillstĂ„ndet för pekaren lp_flags. HĂ€r Ă€r det "normal" – vilket betyder att pekaren verkligen pekar pĂ„ en version av raden. Andra vĂ€rden kommer vi att titta pĂ„ senare.
  • Av alla informationsbitar har vi just nu bara extraherat tvĂ„ par. Bitarna xmin_committed och xmin_aborted visar om transaktionen med nummer xmin har bekrĂ€ftats (eller avbrutits). TvĂ„ motsvarande bitar avser transaktionen med nummer xmax.

Vad ser vi? NÀr en rad infogas pÄ en tabellsida kommer en pekare med nummer 1 att visas, som refererar till den första och enda versionen av raden.

I raden version Àr fÀltet xmin ifyllt med numret för den aktuella transaktionen. Transaktionen Àr fortfarande aktiv, sÄ bÄda bitarna xmin_committed och xmin_aborted Àr inte instÀllda.

FĂ€ltet ctid i raden version refererar till samma rad. Det betyder att en nyare version inte finns.

FÀltet xmax Àr fyllt med det fiktiva numret 0, eftersom denna version av raden inte har tagits bort och Àr aktuell. Transaktioner kommer inte att ta hÀnsyn till detta nummer eftersom biten xmax_aborted Àr instÀlld.

LÄt oss ta ett steg till för att förbÀttra lÀsbarheten genom att lÀgga till informationsbitar till transaktionsnumren. Vi skapar en funktion eftersom vi kommer att behöva den hÀr frÄgan mer Àn en gÄng:

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

I detta format blir det betydligt tydligare vad som hÀnder i rubriken för raden version:

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

Liknande, men betydligt mindre detaljerad information kan ocksÄ erhÄllas direkt frÄn tabellen, genom att anvÀnda pseudokolumnerna xmin och xmax:

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

Commit

Vid en lyckad transaktion mĂ„ste statusen komma ihĂ„g — markera att den har blivit bekrĂ€ftad. Detta sker med en struktur som kallas XACT (och fram till version 10 kallades den CLOG (commit log) och detta namn kan fortfarande förekomma pĂ„ olika stĂ€llen).

XACT — Ă€r ingen systemkatalogtabell; det Ă€r filer i katalogen PGDATA/pg_xact. För varje transaktion tilldelas tvĂ„ bitar: committed och aborted — precis som i rubriken för raden version. Denna information Ă€r uppdelad över flera filer enbart för bekvĂ€mlighetens skull, vi Ă„terkommer till denna frĂ„ga nĂ€r vi diskuterar fryst data. Arbetet med dessa filer sker sidovis, precis som med alla andra.

SÄ, vid registrering av transaktionen i XACT sÀtts biten committed för denna transaktion. Och det Àr allt som hÀnder vid registreringen (Àven om vi Àn sÄ lÀnge inte pratar om förinspelningens logg).

NÀr nÄgon annan transaktion refererar till tabellens sida som vi just har tittat pÄ, mÄste den besvara nÄgra frÄgor.

  1. Har transaktionen xmin avslutats? Om inte, ska den skapade versionen av raden inte vara synlig.
    En sÄdan kontroll görs genom att granska en annan struktur som ligger i den gemensamma minnet för instansen och kallas ProcArray. HÀr finns en lista över alla aktiva processer, och för varje anges numret pÄ dess aktuella (aktiva) transaktion.
  2. Om den har avslutats, hur – genom registrering eller avbokning? Om avbokning, ska versionen av raden inte heller vara synlig.
    Det Àr just dÀrför som XACT behövs. Men, Àven om de senaste sidorna av XACT lagras i buffrar i det aktiva minnet, Àr det kostsamt att kontrollera XACT varje gÄng. DÀrför skrivs den faststÀllda statusen för transaktionen en gÄng för alla i bitarna xmin_committed och xmin_aborted för versionen av raden. Om nÄgon av dessa bitar Àr instÀlld, anses statusen för transaktionen xmin vara kÀnd, och nÀsta transaktion behöver inte lÀngre referera till XACT.

Varför sÀtts inte dessa bitar av sjÀlva transaktionen som utför insÀttningen? NÀr insÀttningen sker vet transaktionen Ànnu inte om den kommer att avslutas framgÄngsrikt. Och vid registreringen Àr det redan oklart vilka specifika rader som har Àndrats pÄ vilka specifika sidor. Det kan vara mÄnga sÄdana sidor, och att minnas dem Àr inte fördelaktigt. Dessutom kan delar av sidorna ha spolas ut frÄn buffertcache till disk; att lÀsa dem igen för att Àndra bitarna skulle avsevÀrt sakta ner registreringen.

Den baksidan av besparingen Ă€r att efter Ă€ndringarna kan vilken transaktion som helst (Ă€ven en som endast utför enkel lĂ€sning – SELECT) börja Ă€ndra databasens sidor i buffertcachen.

SÄ, lÄt oss registrera förÀndringen.

=> COMMIT;

Inga Àndringar har gjorts i sidan (men vi vet att statusen för transaktionen redan har registrerats i XACT):

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

Nu mÄste transaktionen, som först refererade till sidan, faststÀlla statusen för transaktionen xmin och registrera den i informationsbitarna:

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

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

Ta bort

Vid borttagning av en rad i fÀltet xmax för den aktuella versionen registreras numret för den aktuella borttagande transaktionen, och biten xmax_aborted ÄterstÀlls.

LÄt oss notera att det faststÀllda vÀrdet xmax, som motsvarar den aktiva transaktionen, fungerar som en radlÄsning. Om en annan transaktion försöker uppdatera eller ta bort denna rad, kommer den att tvingas vÀnta pÄ att transaktionen xmax ska avslutas. Mer om lÄsningar kommer vi att diskutera senare. Samtidigt noterar vi bara att antalet radlÄsningar inte Àr begrÀnsat. De tar ingen plats i RAM-minnet och systemets prestanda pÄverkas inte av deras antal. Visserligen har 'lÄnga' transaktioner andra nackdelar, men det kommer vi ocksÄ att ta upp senare.

Vi tar bort raden.

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

Vi ser att transaktionsnumret har registrerats i fÀltet xmax, men informationsbitarna Àr inte instÀllda:

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

Återkallande

Återkallande av Ă€ndringar fungerar pĂ„ samma sĂ€tt som bekrĂ€ftelse, men i XACT sĂ€tts en bit för transaktionen till aborted. Återkallandet utförs lika snabbt som en bekrĂ€ftelse. Även om kommandot heter ROLLBACK, sker ingen Ă„ngring av Ă€ndringar: allt som transaktionen har lyckats Ă€ndra i databladen förblir oförĂ€ndrat.

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

Vid Ätkomst av sidan kommer statusen att kontrolleras och biten för hint xmax_aborted kommer att stÀllas in i versionen av raden. SjÀlva numret xmax förblir pÄ sidan, men ingen kommer att titta pÄ det lÀngre.

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

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

Uppdatering

Uppdatering fungerar sÄ, som om den nuvarande versionen av raden först tog bort den gamla raden och sedan införde en ny.

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

FrÄgan returnerar en rad (den nya versionen):

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

Men pÄ sidan ser vi bÄda versionerna:

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

Den borttagna versionen Àr mÀrkt med numret för den aktuella transaktionen i fÀltet xmax. Dessutom Àr detta vÀrde skrivet över det gamla, eftersom den föregÄende transaktionen har avbrutits. Och biten xmax_aborted Àr ÄterstÀlld eftersom statusen för den aktuella transaktionen fortfarande Àr okÀnd.

Den första versionen av raden refererar nu till den andra (fÀltet t_ctid), som till den mer aktuella.

PÄ indexsidan visas en andra pekare och en andra rad som hÀnvisar till den andra versionen pÄ tabellsidan.

Som vid borttagning tjÀnar vÀrdet xmax i den första versionen av raden som tecken pÄ att raden Àr lÄst.

Och vi avslutar transaktionen.

=> COMMIT;

Index

Än sĂ„ lĂ€nge har vi bara pratat om tabellsidor. Vad hĂ€nder inuti indexen?

Informationen pĂ„ indexsidor beror starkt pĂ„ den specifika typen av index. Även inom en och samma typ av index kan det finnas olika typer av sidor. Till exempel har B-trĂ€det en sida med metadata och "vanliga" sidor.

Vanligtvis innehÄller sidan dock en array av pekare till rader och sjÀlva raderna (precis som pÄ tabellsidan). Dessutom avsÀtts utrymme i slutet av sidan för specialdata.

Raderna i index kan ocksÄ ha mycket olika strukturer beroende pÄ typen av index. Till exempel innehÄller rader som hör till bladsidor i B-trÀdet vÀrdet av indexnyckeln och en referens (ctid) till den motsvarande raden i tabellen. I allmÀnhet kan ett index vara helt annorlunda uppbyggt.

Den viktigaste punkten Ă€r att det i index av nĂ„got slag inte finns nĂ„gra versioner av rader. Eller man kan sĂ€ga att varje rad representeras av exakt en version. Med andra ord finns det inga fĂ€lt xmax och xmin i rubriken för indexraden. Man kan anta att pekarna frĂ„n indexet pekar pĂ„ alla tabellversioner av raderna — sĂ„ att reda ut vilken version en transaktion kommer att se kan man endast göra genom att titta i tabellen. (Som vanligt Ă€r detta inte hela sanningen. I vissa fall möjliggör synkartan optimering av processen, men vi kommer att titta nĂ€rmare pĂ„ det senare.)

DÀrmed upptÀcker vi pekare pÄ bÄda versionerna pÄ indexsidan, bÄde den aktuella och den gamla:

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

Virtuella transaktioner

I praktiken anvÀnder PostgreSQL en optimering som gör att man kan "spara" transaktionsnummer.

Om en transaktion bara lÀser data pÄverkar den inte synbarheten av versioner av rader. DÀrför tilldelar den hanterande processen först en virtuell nummer (virtual xid) till transaktionen. Numret bestÄr av processens identifierare och ett sekventiellt tal.

Utdelning av detta nummer krÀver ingen synkronisering mellan alla processer och utförs dÀrför mycket snabbt. En annan anledning till att anvÀnda virtuella nummer kommer vi att bekanta oss med nÀr vi pratar om frysningsprocessen.

Virtuella nummer beaktas inte i databilder.

Vid olika tidpunkter i systemet kan det finnas virtuella transaktioner med nummer som redan har anvÀnts, och det Àr normalt. Men ett sÄdant nummer kan inte skrivas in i databilder, eftersom det vid nÀsta tillgÄng till sidan kan tappa all mening.

=> BÖRJA;
=> VÄLJ txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 rad)

Om transaktionen börjar förÀndra data tilldelas den ett verkligt, unikt transaktionsnummer.

=> UPPDATERA konton STÄLL IN belopp = belopp - 1.00;
=> VÄLJ txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 rad)

=> COMMIT;

NĂ€sta transaktioner

SĂ€kerhetspunkter

I SQL definieras sÀkerhetspunkter (savepoint), som gör det möjligt att Äterkalla en del av transaktionsoperationerna utan att avbryta hela. Men detta passar inte in i det ovan nÀmnda schemat, eftersom statusen för transaktionen Àr en för alla dess förÀndringar, och inga data Ängras fysiskt.

För att implementera en sÄdan funktionalitet delas en transaktion med en sÀkerhetspunkt upp i flera separata nÀsta transaktioner (subtransaction), vars status kan hanteras separat.

NÀsta transaktioner har sitt eget nummer (större Àn numret för huvudtransaktionen). Statusen för nÀsta transaktioner sparas pÄ vanligt sÀtt i XACT, men det slutgiltiga lÀget beror pÄ statusen för huvudtransaktionen: om den avbryts avbryts Àven alla nÀsta transaktioner.

Information om nĂ€stlade transaktioner lagras i filer i katalogen PGDATA/pg_subtrans. Åtkomst till filerna sker via buffrar i den gemensamma minnet av instansen, organiserade pĂ„ samma sĂ€tt som buffrarna i XACT.

Blanda inte ihop nÀstlade transaktioner och autonoma transaktioner. Autonoma transaktioner Àr helt oberoende av varandra, medan nÀstlade Àr beroende. Det finns inga autonoma transaktioner i vanlig PostgreSQL, och det Àr nog för det bÀsta: de behövs sÀllan och deras förekomst i andra databashanteringssystem ger ofta upphov till missbruk som alla lider av senare.

LÄt oss rensa tabellen, börja transaktionen och sÀtta in en rad:

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

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

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

Nu sÀtter vi en sparpunkt och lÀgger till ytterligare en rad.

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

Observera att funktionen txid_current() ger huvudtransaktionens nummer, inte den inre transaktionen.

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

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

LÄt oss ÄtergÄ till sparpunkten och lÀgga till den tredje raden.

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

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

PĂ„ sidan ser vi fortfarande raden som lagts till av den avbrutna inre transaktionen.

Spara Àndringarna.

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

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

Nu Àr det tydligt att varje inre transaktion har sin egen status.

Observera att inre transaktioner inte kan anvÀndas uttryckligen i SQL, det vill sÀga att man inte kan starta en ny transaktion utan att avsluta den aktuella. Denna mekanism aktiveras implicit vid anvÀndning av sparpunkter, liksom vid hantering av undantag i PL/pgSQL och i andra mer exotiska fall.

=> BEGIN;
BEGIN
=> BEGIN;
VARNING:  det finns redan en pÄgÄende transaktion
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
VARNING:  det finns ingen pÄgÄende transaktion
COMMIT

Fel och atomaritet i operationerna

Vad hÀnder om ett fel intrÀffar under utförandet av en operation? Till exempel:

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

=> UPDATE t SET s = repeat('X', 1/(id-4));
FEL:  division med noll

Ett fel har intrÀffat. Nu anses transaktionen vara avbruten och inga operationer Àr tillÄtna i den:

=> SELECT * FROM t;
FEL:  aktuell transaktion Àr avbruten, kommandon ignoreras tills transaktionsblockets slut

Och Àven om man försöker spara Àndringarna kommer PostgreSQL att meddela om avbrottet:

=> COMMIT;
ROLLBACK

Varför kan man inte fortsĂ€tta med en transaktion efter ett fel? Det beror pĂ„ att felet kan ha intrĂ€ffat pĂ„ ett sĂ€tt sĂ„ att vi skulle ha fĂ„tt Ă„tkomst till en del av Ă€ndringarna — atomariteten hos inte bara transaktionen utan Ă€ven operatören skulle ha brutits. Som i vĂ„rt exempel dĂ€r operatören hann uppdatera en rad innan felet intrĂ€ffade:

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

Det ska nÀmnas att psql har ett lÀge som faktiskt tillÄter att transaktionen fortsÀtter efter ett fel, som om de felaktiga operationerna rullas tillbaka.

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

=> UPDATE t SET s = repeat('X', 1/(id-4));
FEL:  division med noll

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

=> COMMIT;

Det Àr lÀtt att förstÄ att psql i detta lÀge faktiskt sÀtter en oexplicit ÄterstÀllningspunkt före varje kommando, och vid ett fel initierar det en ÄterstÀllning till den. Detta lÀge anvÀnds inte som standard, eftersom det medför betydande overheadkostnader att sÀtta ÄterstÀllningspunkter (Àven utan att rulla tillbaka till dem).

FortsÀttning.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster