MVCC-3. Versjoner av rader

SĂ„, vi har sett pĂ„ spĂžrsmĂ„l knyttet til isolering, og vi har gjort et avsnitt om organisering av data pĂ„ lavt nivĂ„. Og til slutt har vi kommet til det mest interessante — til versjoner av rader.

Header

Som vi allerede har sagt, kan hver rad samtidig eksistere i databasen i flere versjoner. En versjon mĂ„ skille seg fra en annen. Til dette formĂ„let har hver versjon to merker som definerer 'tiden' nĂ„r denne versjonen er aktiv (xmin og xmax). I anfĂžrselstegn — fordi det ikke er tid som sĂ„dan som brukes, men en spesiell Ăžkende teller. Og denne telleren er transaksjonsnummeret.

(Som vanlig er alt mer komplisert: transaksjonsnummeret kan ikke Þke hele tiden pÄ grunn av den begrensede bitstÞrrelsen til telleren. Men disse detaljene vil vi se nÊrmere pÄ nÄr vi kommer til frysing.)

NÄr en rad opprettes, settes verdien av xmin til transaksjonsnummeret som utfÞrte INSERT-kommandoen, mens xmax ikke fylles ut.

NÄr en rad slettes, merkes verdien av xmax for den nÄvÊrende versjonen med transaksjonsnummeret som utfÞrte DELETE.

NÄr en rad endres med UPDATE-kommandoen, utfÞres det faktisk to operasjoner: DELETE og INSERT. I den nÄvÊrende versjonen av raden settes xmax til transaksjonsnummeret som utfÞrte UPDATE. SÄ opprettes en ny versjon av den samme raden; verdien av xmin samsvarer med verdien av xmax for den forrige versjonen.

Feltene xmin og xmax inngÄr i versjonens hode. I tillegg til disse feltene inneholder hodet andre, for eksempel:

  • infomask — en rekke biter som definerer egenskapene til denne versjonen. Det er ganske mange av dem; de viktigste vil vi gradvis se pĂ„.
  • ctid — en referanse til den neste, nyere versjonen av den samme raden. I den nyeste, aktuelle versjonen av raden, peker ctid til selve denne versjonen. Nummeret har formatet (x,y), der x er sidetallet, y er den sekvensielle nummeret i tabellen.
  • bitkart for usikre verdier — markerer de kolonnene i denne versjonen som inneholder usikker verdi (NULL). NULL er ikke et vanlig datatyperverdi, sĂ„ dette mĂ„ holde seg separat.

Som et resultat blir hodet ganske stort — minst 23 byte for hver versjon av raden, og vanligvis mer pĂ„ grunn av NULL-bitmapen. Hvis tabellen er 'smal' (det vil si inneholder fĂ„ kolonner), kan overheaden ta mer enn den nyttige informasjonen.

InnfĂžring

La oss se nÊrmere pÄ hvordan operasjoner med strenger utfÞres pÄ lavt nivÄ, og begynn med innsetting.

For eksperimenter skal vi opprette en ny tabell med to kolonner og en indeks pÄ en av dem:

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

Vi setter inn én rad etter Ä ha startet en transaksjon.

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

Her er nummeret pÄ vÄr nÄvÊrende transaksjon:

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

La oss ta en titt pÄ innholdet i siden. Funksjonen heap_page_items fra utvidelsen pageinspect gjÞr det mulig Ä hente informasjon om pekere og versjoner av strenger:

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

Merk at ordet heap (haug) i PostgreSQL refererer til tabeller. Dette er enda et merkelig bruk av termen — haug er en kjent datastruktur, som ikke har noe med tabellen Ă„ gjĂžre. Her brukes ordet i betydningen «alt er kastet i en haug», i motsetning til ordnede indekser.

Funksjonen viser data «som de er», i et format som er vanskelig Ä forstÄ. For Ä fÄ klarhet, skal vi begrense informasjonen og tolke den:

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

Slik var det vi gjorde:

  • Vi la til en null til pekernumret for Ă„ fĂ„ det til Ă„ se likt ut som t_ctid: (side nummer, pekernummer).
  • Vi tolket tilstanden til pekeren lp_flags. Her er den 'normal' — dette betyr at pekeren faktisk refererer til versjonen av strengen. Andre verdier vil vi se pĂ„ senere.
  • Av alle informasjonsbitene har vi valgt ut to par. Bitene xmin_committed og xmin_aborted viser om transaksjonen med nummer xmin er bekreftet (eller kansellert). To tilsvarende biter gjelder transaksjonen med nummer xmax.

Hva ser vi? NÄr vi setter inn en rad pÄ en tabellside, vil det vises en peker med nummer 1 som refererer til den fÞrste og eneste versjonen av raden.

I versjonen av raden er feltet xmin fylt med nummeret for den aktuelle transaksjonen. Transaksjonen er fortsatt aktiv, sÄ begge bitene xmin_committed og xmin_aborted er ikke satt.

Feltet ctid i versjonen av raden refererer til denne samme raden. Dette betyr at det ikke finnes en nyere versjon.

Feltet xmax er fylt med en fiktiv nummer 0, siden denne versjonen av raden ikke er slettet og er aktuell. Transaksjoner vil ikke ta hensyn til dette nummeret, siden biten xmax_aborted er satt.

La oss ta et ekstra steg for Ă„ forbedre lesbarheten ved Ă„ legge til informasjon om bitene til transaksjonsnumrene. Og vi oppretter en funksjon, siden forespĂžrselen vil vĂŠre nyttig flere ganger:

=> 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 denne formen er det mye klarere hva som skjer i overskriften til versjonen av raden:

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

Lignende, men betydelig mindre detaljert informasjon kan ogsÄ innhentes fra selve tabellen ved Ä bruke pseudokolonnene xmin og xmax:

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

Forpliktelse

Ved vellykket fullfĂžring av transaksjonen mĂ„ vi huske dens status — merke den som bekreftet. Dette gjĂžres ved hjelp av en struktur kalt XACT (og fĂžr versjon 10 ble den kalt CLOG (committ log), et navn som fortsatt kan forekomme pĂ„ forskjellige steder).

XACT — ikke en systemkatalogtabell; dette er filer i katalogen PGDATA/pg_xact. I disse er det avsatt to biter for hver transaksjon: committed og aborted — pĂ„ akkurat samme mĂ„te som i overskriften til versjonen av raden. Denne informasjonen er delt mellom flere filer utelukkende for praktisk skyld, vi vil komme tilbake til dette spĂžrsmĂ„let nĂ„r vi ser pĂ„ frysing. Arbeidet med disse filene gjĂžres side for side, akkurat som med alle andre.

SÄ nÄr en transaksjon blir bekreftet i XACT settes biten committed for den transaksjonen. Og det er alt som skjer ved bekreftelse (selv om vi ikke snakker om pre-write loggen ennÄ).

NÄr en annen transaksjon prÞver Ä fÄ tilgang til tabell-siden som vi nettopp sÄ pÄ, mÄ den svare pÄ noen spÞrsmÄl.

  1. Har transaksjonen xmin blitt fullfĂžrt? Hvis ikke, skal ikke den opprettede versjonen av raden vĂŠre synlig.
    Slik sjekk utfÞres ved Ä se pÄ en annen struktur som ligger i den delte minnen til instansen og kalles ProcArray. Den inneholder en liste over alle aktive prosesser, og for hver av dem er nummeret pÄ deres nÄvÊrende (aktive) transaksjon angitt.
  2. Hvis den er fullfþrt, hvordan ble den det — ved bekreftelse eller avvisning? Hvis det var en avvisning, skal heller ikke versjonen av raden vére synlig.
    Det er nettopp dette som XACT er til for. Men selv om de siste sidene i XACT lagres i buffere i minnet, er det fortsatt kostbart Ä sjekke XACT hver gang. Derfor blir statusen til transaksjonen lagret i bitene xmin_committed og xmin_aborted for versjonen av raden. Hvis en av disse bitene er satt, anses statusen til transaksjonen xmin som kjent, og den pÄfÞlgende transaksjonen trenger ikke Ä gÄ til XACT.

Hvorfor settes disse bitene ikke av selve transaksjonen som utfÞrer innsettingen? NÄr innsetting skjer, vet ikke transaksjonen enda om den vil bli vellykket. Og pÄ tidspunktet for bekreftelsen er det ukjent hvilke rader som har blitt endret pÄ hvilke sider. Det kan vÊre mange slike sider, og det er ugunstig Ä huske dem. I tillegg kan deler av sidene ha blitt utvist fra buffercache til disk; Ä lese dem igjen for Ä endre bitene ville betydelig forsinke bekreftelsen.

Den bakside av besparelsen er at etter endringer kan enhver transaksjon (selv en som bare utfĂžrer lesing — SELECT) begynne Ă„ endre databladene i bufferCache.

SĂ„, la oss bekrefte endringen.

=> COMMIT;

Ingenting har endret seg pÄ siden (men vi vet at statusen til transaksjonen allerede er lagret i XACT):

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

NÄ mÄ transaksjonen som fÞrst fikk tilgang til siden, bestemme statusen til transaksjonen xmin og skrive den til informasjonsbitene:

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

Sletting

NÄr en rad slettes, blir nummeret pÄ den nÄvÊrende slettetransaksjonen skrevet inn i feltet xmax, mens biten xmax_aborted tilbakestilles.

Vi merker at den angitte verdien xmax, som tilsvarer den aktive transaksjonen, fungerer som en radlÄs. Hvis en annen transaksjon prÞver Ä oppdatere eller slette denne raden, mÄ den vente pÄ at transaksjonen xmax fullfÞres. Vi vil gÄ dypere inn i lÄser senere. ForelÞpig vil vi bare pÄpeke at antallet radlÄser ikke er begrenset. De tar ikke plass i RAM, og systemets ytelse pÄvirkes ikke av hvor mange de er. Imidlertid har 'lange' transaksjoner andre ulemper, men mer om det senere.

Vi sletter raden.

=> BEGYNNE;
=> SLETTE FRA t;
=> VELG txid_current();
 txid_current 
--------------
         3665
(1 rad)

Vi ser at transaksjonsnummeret ble skrevet inn i feltet xmax, men informasjonsbitene er ikke satt:

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

Avbryt

Avbryt av endringer fungerer pÄ samme mÄte som bekreftelse, bortsett fra at i XACT for transaksjonen settes biten aborted. Avbrytelsen skjer like raskt som bekreftelsen. Selv om kommandoen kalles ROLLBACK, skjer det ingen tilbakestilling av endringer: alt som transaksjonen har rakk Ä endre i datadene, forblir uendret.

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

NÄr du fÄr tilgang til siden, vil statusen bli kontrollert, og biten xmax_aborted blir satt i versjonen av raden. Selve nummeret xmax forblir pÄ siden, men ingen vil se pÄ det lenger.

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

Oppdatering

Oppdateringen fungerer som om den nÄvÊrende versjonen av raden fÞrst ble slettet, og deretter ble en ny satt inn.

=> BEGYNNE;
=> OPPDATERE t SET s = 'BAR';
=> VELG txid_current();
 txid_current 
--------------
         3666
(1 rad)

ForespĂžrselen gir en rad (den nye versjonen):

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

Men pÄ siden ser vi begge versjoner:

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

Den slettede versjonen er merket med nummeret pÄ den nÄvÊrende transaksjonen i feltet xmax. Dette tallet er skrevet over det gamle, siden den forrige transaksjonen ble avbrutt. Og biten xmax_aborted er tilbakestilt, da statusen til den nÄvÊrende transaksjonen fortsatt er ukjent.

Den fÞrste versjonen av raden refererer nÄ til den andre (feltet t_ctid) som den nyere.

I indeks siden vises en annen peker og en annen rad som refererer til den andre versjonen pÄ tabellsiden.

Akkurat som ved sletting, fungerer vmax i den fÞrste versjonen av raden som et tegn pÄ at raden er lÄst.

Og nÄ avslutter vi transaksjonen.

=> COMMIT;

Indexes

SĂ„ langt har vi bare snakket om tabellsider. Hva skjer imidlertid inne i indeksene?

Informasjonen i indeks sider avhenger sterkt av den spesifikke typen indeks. Og selv innenfor en type indeks kan det vÊre forskjellige typer sider. For eksempel har et B-tre en side med metadata og «vanlige» sider.

Likevel inneholder siden vanligvis et array av pekere til rader og de faktiske radene (akkurat som pÄ tabellsiden). I tillegg er det avsatt plass til spesielle data pÄ slutten av siden.

Radene i indeksene kan ogsÄ ha svÊrt ulik struktur avhengig av type indeks. For eksempel, for B-treet inneholder radene som er knyttet til blad sider, nÞkkelen til indeksen og en referanse (ctid) til den aktuelle raden i tabellen. Generelt kan indeksen vÊre helt annerledes strukturert.

Det viktigste punktet er at det i indekser av noe slag ikke finnes radversjoner. Eller man kan si at hver rad er representert med akkurat én versjon. Med andre ord, det finnes ikke feltene xmin og xmax i overskriften til indeksraden. Man kan si at referansene fra indeksen peker til alle tabellversjoner av radene - sÄ man kan bare finne ut hvilken versjon transaksjonen vil se ved Ä se i tabellen. (Som vanlig, dette er ikke hele sannheten. I noen tilfeller kan synlighetskartet optimalisere prosessen, men vi skal se nÊrmere pÄ det senere.)

I indekssiden finner vi pekere til begge versjoner, bÄde den aktuelle og den gamle:

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

Virtuelle transaksjoner

I praksis bruker PostgreSQL optimalisering som gjÞr at man kan «sparing» transaksjonsnumre.

Hvis en transaksjon bare leser data, pÄvirker den ikke synligheten av radversjoner. Derfor tildeler serverprosessen fÞrst en virtuell nummer (virtual xid) til transaksjonen. Nummeret bestÄr av prosess-ID og et sekvensnummer.

Utdeling av dette nummeret krever ikke synkronisering mellom alle prosessene og utfÞres derfor veldig raskt. En annen Ärsak til bruken av virtuelle nummer vil vi bli kjent med nÄr vi snakker om frysing.

Virtuelle nummer blir ikke tatt med i databilder.

PĂ„ forskjellige tidspunkter kan det befinne seg virtuelle transaksjoner i systemet med numre som allerede er brukt, og det er normalt. Men et slikt nummer kan ikke skrives til databilder, fordi det ved neste tilgang til siden kan miste all mening.

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

Hvis en transaksjon begynner Ă„ endre data, tildeles den et ekte, unikt transaksjonsnummer.

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

=> COMMIT;

Innebygde transaksjoner

Lagrede punkter

I SQL er det definert lagrede punkter (savepoint), som lar deg angre deler av en transaksjon uten Ă„ avbryte den helt. Men dette passer ikke inn i det angitte skjemaet, siden statusen for en transaksjon er lik for alle dens endringer, og ingen data blir fysisk tilbakestilt.

For Ă„ implementere slik funksjonalitet, deles en transaksjon med lagret punkt opp i flere separate innebygde transaksjoner (subtransaction), hvis status kan administreres separat.

Innebygde transaksjoner har sitt eget nummer (stÞrre enn nummeret til hovedtransaksjonen). Statusen til de innebygde transaksjonene lagres pÄ vanlig mÄte i XACT, men den endelige statusen avhenger av statusen til hovedtransaksjonen: hvis den avbrytes, avbrytes ogsÄ alle innebygde transaksjoner.

Informasjonen om innebygde transaksjoner lagres i filer i katalogen PGDATA/pg_subtrans. Tilgangen til filene skjer via buffer i det delte minnet til instansen, organisert pÄ samme mÄte som bufferne i XACT.

Ikke forveksle innebygde transaksjoner med autonome transaksjoner. Autonome transaksjoner er uavhengige av hverandre, mens innebygde avhenger av hverandre. Autonome transaksjoner finnes ikke i vanlig PostgreSQL, og det er kanskje til det beste: de er veldig sjeldne i bruk, og tilstedevĂŠrelsen deres i andre DBMS fĂžrer ofte til misbruk som alle senere lider under.

Vi rydder opp i tabellen, starter transaksjonen og setter inn en rad:

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

No putt vi ein checkpoint og set fa en ny rad.

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

Merk at funksjonen txid_current() gir tilbake nummeret til den ytre, og ikke den indre, transaksjonen.

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

Vi vil rulle tilbake til checkpoint og sette inn en tredje rad.

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

PĂ„ siden ser vi fortsatt raden fra den avbrutte indre transaksjonen.

Vi fastsetter endringene.

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

NĂ„ er det klart at hver indre transaksjon har sin egen status.

Merk at indre transaksjoner ikke kan brukes eksplisitt i SQL, det vil si at du ikke kan starte en ny transaksjon fÞr den nÄvÊrende er fullfÞrt. Denne mekanismen brukes implisitt med bruk av lagringspunkter, samt ved hÄndtering av unntak i PL/pgSQL og i flere andre mer eksotiske tilfeller.

=> BEGIN;
BEGIN
=> BEGIN;
ADVARSEL:  det er allerede en transaksjon i gang
BEGIN
=> COMMIT;
FORPLIKT
=> COMMIT;
ADVARSEL:  det er ingen transaksjon i gang
COMMIT

Feil og atomaritet av operasjoner

Hva skjer dersom det oppstÄr en feil under utfÞrelsen av operasjonen? For eksempel:

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

=> UPDATE t SET s = repeat('X', 1/(id-4));
FEIL:  divisjon med null

Det oppsto en feil. NĂ„ betraktes transaksjonen som avbrutt, og ingen operasjoner er tillatt innenfor den:

=> SELECT * FROM t;
FEIL:  gjeldende transaksjon er avbrutt, kommandoer blir ignorert inntil slutten av transaksjonsblokken

Og selv om man forsĂžker Ă„ fastsette endringene, vil PostgreSQL rapportere at transaksjonen ble avbrutt:

=> COMMIT;
ROLLBACK

Hvorfor kan man ikke fortsette transaksjonen etter en feil? Det er fordi feilen kan ha oppstĂ„tt pĂ„ en mĂ„te som fĂžrte til at vi fikk tilgang til en del av endringene — atomisiteten til ikke bare transaksjonen, men ogsĂ„ operatĂžren ville vĂŠrt brutt. Som i vĂ„rt eksempel, hvor operatĂžren rakk Ă„ oppdatere Ă©n rad fĂžr feilen inntraff:

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

Det mÄ sies at psql har en modus som faktisk lar deg fortsette transaksjonen etter en feil, som om handlingene utfÞrt av den feilaktige operatÞren blir rullet tilbake.

=> 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));
FEIL:  divisjon med null

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

=> COMMIT;

Det er ikke vanskelig Ä forstÄ at i en slik modus setter psql faktisk en usynlig lagringspunkt foran hver kommando, og i tilfelle av en feil initierer det en tilbakefÞring til dette punktet. Denne modusen brukes ikke som standard, ettersom oppsett av lagringspunkter (selv uten tilbakefÞring) medfÞrer betydelige overheadkostnader.

Fortsettelse.

Kilde: habr.com

KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster