MVCC-3. Stringversjoner

Så vi har vurdert problemer knyttet til isolasjon, og gjorde en retrett om organisere data på et lavt nivå. Og til slutt kom vi til den mest interessante delen - strengversjonene.

Tittel

Som vi allerede har sagt, kan hver rad samtidig eksistere i flere versjoner i databasen. En versjon må på en eller annen måte skilles fra en annen. For dette formålet har hver versjon to merker som bestemmer "tidspunktet" for handlingen til denne versjonen (xmin og xmax). I anførselstegn – for det er ikke tiden som sådan som brukes, men en spesiell økende teller. Og denne telleren er transaksjonsnummeret.

(Som vanlig er virkeligheten mer komplisert: transaksjonsnummeret kan ikke øke hele tiden på grunn av den begrensede bitkapasiteten til telleren. Men vi vil se på disse detaljene i detalj når vi begynner å fryse.)

Når en rad opprettes, settes xmin til transaksjonsnummeret som ga INSERT-kommandoen, og xmax står tomt.

Når en rad slettes, merkes xmax-verdien til gjeldende versjon med nummeret på transaksjonen som utførte SLETT.

Når en rad modifiseres av en UPDATE-kommando, utføres faktisk to operasjoner: DELETE og INSERT. Den gjeldende versjonen av raden setter xmax lik nummeret på transaksjonen som utførte OPPDATERING. En ny versjon av den samme strengen opprettes deretter; xmin-verdien sammenfaller med xmax-verdien til forrige versjon.

xmin- og xmax-feltene er inkludert i radversjonsoverskriften. I tillegg til disse feltene inneholder overskriften andre, for eksempel:

  • infomask er en serie biter som definerer egenskapene til denne versjonen. Det er ganske mange av dem; Vi vil gradvis vurdere de viktigste.
  • ctid er en lenke til neste, nyere versjon av samme linje. For den nyeste, mest oppdaterte versjonen av en linje, refererer ctid til denne versjonen selv. Tallet har formen (x,y), der x er sidetallet, y er indeksnummeret i matrisen.
  • null punktgrafikk - Markerer de kolonnene i en gitt versjon som inneholder en nullverdi (NULL). NULL er ikke en av de normale datatypeverdiene, så attributtet må lagres separat.

Som et resultat er overskriften ganske stor - minst 23 byte for hver versjon av linjen, og vanligvis mer på grunn av NULL bitmap. Hvis tabellen er "smal" (det vil si inneholder få kolonner), kan overheaden ta opp mer enn nyttig informasjon.

sette inn

La oss se nærmere på hvordan strengoperasjoner på lavt nivå utføres, og starter med innsetting.

For eksperimenter, la oss lage 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);

La oss sette inn en rad etter å ha startet en transaksjon.

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

Her er vårt nåværende transaksjonsnummer:

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

La oss se på innholdet på siden. Heap_page_items-funksjonen til pageinspect-utvidelsen lar deg få informasjon om pekere og radversjoner:

=> 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 i PostgreSQL refererer til tabeller. Dette er nok en merkelig bruk av begrepet - en haug er kjent data struktur, som ikke har noe til felles med tabellen. Her brukes ordet i betydningen "alt er kastet sammen", i motsetning til ordnede indekser.

Funksjonen viser data "som de er", i et format som er vanskelig å forstå. For å finne ut av det, vil vi bare la igjen en del av informasjonen og tyde 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)

Her er hva vi gjorde:

  • Lagt til en null til indeksnummeret for å få det til å se likt ut som t_ctid: (sidenummer, indeksnummer).
  • Dechiffrerte tilstanden til lp_flags-pekeren. Her er det "normalt" - dette betyr at pekeren faktisk refererer til versjonen av strengen. Vi skal se på andre betydninger senere.
  • Av alle informasjonsbitene er bare to par identifisert så langt. Bitene xmin_committed og xmin_aborted indikerer om transaksjonsnummeret xmin er begått (avbrutt). To like biter refererer til transaksjonsnummer xmax.

Hva ser vi? Når du setter inn en rad, vil et indeksnummer 1 vises på tabellsiden, som peker til den første og eneste versjonen av raden.

I strengversjonen er xmin-feltet fylt med gjeldende transaksjonsnummer. Transaksjonen er fortsatt aktiv, så både xmin_committed og xmin_aborted bitene er ikke satt.

Radversjon ctid-feltet refererer til samme rad. Dette betyr at en nyere versjon ikke eksisterer.

Xmax-feltet er fylt med et dummynummer 0 fordi denne versjonen av raden ikke er slettet og er gjeldende. Transaksjoner vil ikke ta hensyn til dette nummeret fordi xmax_aborted-biten er satt.

La oss ta enda et skritt mot å forbedre lesbarheten ved å legge til informasjonsbiter i transaksjonsnumre. Og la oss lage en funksjon, siden vi trenger forespørselen mer enn én gang:

=> 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 dette skjemaet er det mye tydeligere hva som skjer i overskriften på radversjonen:

=> 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 hentes fra selve tabellen ved å bruke pseudokolonner xmin og xmax:

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

Fiksering

Hvis en transaksjon er fullført, må du huske statusen - merk at den er forpliktet. For å gjøre dette brukes en struktur kalt XACT (og før versjon 10 ble den kalt CLOG (commit log) og dette navnet kan fortsatt finnes på forskjellige steder).

XACT er ikke en systemkatalogtabell; dette er filene i katalogen PGDATA/pg_xact. De har to biter for hver transaksjon: begått og avbrutt - akkurat som i radversjonsoverskriften. Denne informasjonen er delt inn i flere filer kun for enkelhets skyld; vi kommer tilbake til dette problemet når vi vurderer å fryse. Og arbeidet med disse filene utføres side for side, som med alle andre.

Så når en transaksjon er forpliktet i XACT, settes den forpliktede biten for denne transaksjonen. Og dette er alt som skjer under committing (selv om vi ikke snakker om forhåndsopptaksloggen ennå).

Når en annen transaksjon får tilgang til tabellsiden vi nettopp så på, må den svare på flere spørsmål.

  1. Er xmin-transaksjonen fullført? Hvis ikke, bør den opprettede versjonen av strengen ikke være synlig.
    Denne kontrollen utføres ved å se på en annen struktur, som ligger i det delte minnet til forekomsten og kalles ProcArray. Den inneholder en liste over alle aktive prosesser, og for hver enkelt er nummeret på dens gjeldende (aktive) transaksjon angitt.
  2. Hvis fullført, hvordan - ved å binde eller kansellere? Hvis den avbrytes, skal heller ikke radversjonen være synlig.
    Det er akkurat dette XACT er for. Men selv om de siste sidene av XACT er lagret i buffere i RAM, er det fortsatt dyrt å sjekke XACT hver gang. Derfor, når transaksjonsstatusen er bestemt, skrives den til bitene xmin_committed og xmin_aborted i strengversjonen. Hvis en av disse bitene er satt, anses tilstanden til transaksjonen xmin som kjent, og neste transaksjon vil ikke ha tilgang til XACT.

Hvorfor settes ikke disse bitene av transaksjonen selv som gjør innsettingen? Når en innsetting skjer, vet ikke transaksjonen ennå om den vil lykkes. Og i forpliktelsesøyeblikket er det ikke lenger klart hvilke linjer på hvilke sider som ble endret. Det kan være mange slike sider, og det er ulønnsomt å memorere dem. I tillegg kan noen sider kastes ut fra bufferbufferen til disken; å lese dem på nytt for å endre bitene ville bremse commit betydelig.

Ulempen med besparelsene er at etter endringer kan enhver transaksjon (selv en som utfører en enkel lesing - SELECT) begynne å endre datasider i bufferbufferen.

Så la oss fikse endringen.

=> COMMIT;

Ingenting har endret seg på siden (men vi vet at transaksjonsstatusen allerede er registrert 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 får tilgang til siden bestemme xmin-transaksjonsstatusen og skrive den til informasjonsbitene:

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

Fjerning

Når en rad slettes, skrives nummeret til gjeldende slettetransaksjon til xmax-feltet til gjeldende versjon, og xmax_aborted-biten slettes.

Merk at den innstilte verdien på xmax som tilsvarer den aktive transaksjonen fungerer som en radlås. Hvis en annen transaksjon ønsker å oppdatere eller slette denne raden, vil den bli tvunget til å vente på at transaksjonen xmax er fullført. Vi vil snakke mer om blokkering senere. Foreløpig legger vi bare merke til at antallet radlåser er ubegrenset. De tar ikke opp plass i RAM og systemytelsen lider ikke av antallet. Riktignok har "lange" transaksjoner andre ulemper, men mer om det senere.

La oss slette linjen.

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

Vi ser at transaksjonsnummeret er skrevet i xmax-feltet, 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 row)

Avbestilling

Å avbryte endringer fungerer på samme måte som å forplikte seg, bare i XACT er den avbrente biten satt for transaksjonen. Å angre er like raskt som å forplikte seg. Selv om kommandoen kalles ROLLBACK, rulles ikke endringer tilbake: alt som transaksjonen klarte å endre på datasidene forblir uendret.

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

Når siden åpnes, vil statusen bli sjekket og xmax_aborted hint-biten settes til radversjonen. Selve xmax-tallet forblir på siden, men ingen vil se på det.

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

Oppdater

Oppdateringen fungerer som om den først slettet gjeldende versjon av raden og deretter satt inn en ny.

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

Spørringen produserer én linje (ny versjon):

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

Men på siden ser vi begge versjonene:

=> 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 slettede versjonen er merket med gjeldende transaksjonsnummer i xmax-feltet. Dessuten skrives denne verdien over den gamle siden den forrige transaksjonen ble kansellert. Og xmax_aborted-biten slettes fordi statusen til gjeldende transaksjon ikke er kjent ennå.

Den første versjonen av linjen refererer nå til den andre (t_ctid-feltet) som den nyere.

En andre indeks vises på indekssiden og en andre rad refererer til den andre versjonen på tabellsiden.

Akkurat som med sletting, er xmax-verdien i den første versjonen av raden en indikasjon på at raden er låst.

Vel, la oss fullføre transaksjonen.

=> COMMIT;

Indekser

Så langt har vi kun snakket om tabellsider. Hva skjer inne i indeksene?

Informasjonen på indekssidene varierer mye avhengig av den spesifikke typen indeks. Og til og med én type indeks har forskjellige typer sider. For eksempel har et B-tre en metadataside og "vanlige" sider.

Siden har imidlertid vanligvis en rekke pekere til radene og selve radene (akkurat som en tabellside). I tillegg er det på slutten av siden plass til spesialdata.

Rader i indekser kan også ha svært forskjellige strukturer avhengig av type indeks. For et B-tre inneholder for eksempel radene knyttet til bladsider indekseringsnøkkelverdien og en referanse (ctid) til den tilsvarende tabellraden. Generelt kan indeksen struktureres på en helt annen måte.

Det viktigste poenget er at det ikke finnes radversjoner i indekser av noen type. Vel, eller vi kan anta at hver linje er representert av nøyaktig én versjon. Det er med andre ord ingen xmin- og xmax-felt i indeksradoverskriften. Vi kan anta at lenker fra indeksen fører til alle tabellversjoner av radene - så du kan finne ut hvilken versjon transaksjonen vil se kun ved å se på tabellen. (Som alltid er ikke dette hele sannheten. I noen tilfeller kan synlighetskartet optimalisere prosessen, men vi skal se nærmere på dette senere.)

Samtidig finner vi på indekssiden pekere til begge versjonene, både den nåværende 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 optimaliseringer som lar den "lagre" transaksjonsnumre.

Hvis en transaksjon kun leser data, har det ingen effekt på synligheten til radversjoner. Derfor utsteder tjenesteprosessen først en virtuell xid til transaksjonen. Nummeret består av en prosess-ID og et sekvensnummer.

Utstedelse av dette nummeret krever ikke synkronisering mellom alle prosesser og er derfor veldig raskt. Vi vil bli kjent med en annen grunn til å bruke virtuelle tall når vi snakker om frysing.

Virtuelle tall tas ikke i betraktning på noen måte i øyeblikksbilder av data.

På ulike tidspunkt kan det godt være virtuelle transaksjoner i systemet med tall som allerede er brukt, og dette er normalt. Men et slikt tall kan ikke skrives inn i datasider, fordi neste gang siden åpnes kan det miste all mening.

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

Hvis en transaksjon begynner å endre data, får den et reelt, unikt transaksjonsnummer.

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

=> COMMIT;

Nestede transaksjoner

Spar poeng

Definert i SQL spare poeng (sparepunkt), som lar deg kansellere deler av en transaksjon uten å avbryte den helt. Men dette passer ikke inn i diagrammet ovenfor, siden transaksjonen har samme status for alle endringene, og fysisk blir ingen data rullet tilbake.

For å implementere denne funksjonaliteten deles en transaksjon med et lagringspunkt i flere separate nestede transaksjoner (deltransaksjon), hvis status kan administreres separat.

Nestede transaksjoner har sitt eget nummer (høyere enn nummeret på hovedtransaksjonen). Statusen til nestede transaksjoner registreres på vanlig måte i XACT, men den endelige statusen avhenger av statusen til hovedtransaksjonen: hvis den blir kansellert, kanselleres også alle nestede transaksjoner.

Informasjon om transaksjonsnesting lagres i filer i PGDATA/pg_subtrans-katalogen. Filer får tilgang gjennom buffere i forekomstens delte minne, organisert på samme måte som XACT-buffere.

Ikke forveksle nestede transaksjoner med autonome transaksjoner. Autonome transaksjoner er ikke avhengige av hverandre på noen måte, men nestede transaksjoner gjør det. Det er ingen autonome transaksjoner i vanlig PostgreSQL, og kanskje til det beste: de trengs veldig, veldig sjelden, og deres tilstedeværelse i andre DBMS-er provoserer misbruk, som alle deretter lider av.

La oss tømme tabellen, starte en transaksjon og sette inn raden:

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

La oss nå sette inn et lagringspunkt og sette inn en annen linje.

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

Merk at txid_current()-funksjonen returnerer hovedtransaksjonsnummeret, ikke det nestede transaksjonsnummeret.

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

La oss rulle tilbake til lagringspunktet og sette inn den tredje linjen.

=> 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 fortsetter vi å se raden lagt til av den kansellerte nestede transaksjonen.

Vi fikser 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å kan du tydelig se at hver nestede transaksjon har sin egen status.

Merk at nestede transaksjoner ikke kan brukes eksplisitt i SQL, det vil si at du ikke kan starte en ny transaksjon uten å fullføre den gjeldende. Denne mekanismen aktiveres implisitt ved bruk av lagringspunkter, samt ved håndtering av PL/pgSQL-unntak og i en rekke andre, mer eksotiske tilfeller.

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

Feil og atomitet av operasjoner

Hva skjer hvis det oppstår en feil mens du utfører en operasjon? For eksempel slik:

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

Det har oppstått en feil. Nå anses transaksjonen som avbrutt og ingen operasjoner er tillatt i den:

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

Og selv om du prøver å foreta endringene, vil PostgreSQL rapportere en avbrytelse:

=> COMMIT;
ROLLBACK

Hvorfor kan ikke en transaksjon fortsette etter en feil? Faktum er at en feil kan oppstå på en slik måte at vi ville få tilgang til en del av endringene - atomiteten til ikke engang transaksjonen, men operatøren ville bli krenket. Som i vårt eksempel, hvor operatøren klarte å oppdatere én linje før feilen:

=> 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 fortsatt lar transaksjonen fortsette etter en feil som om handlingene til den feilaktige operatøren ble 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));
ERROR:  division by zero

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

=> COMMIT;

Det er ikke vanskelig å gjette at i denne modusen setter psql faktisk et implisitt lagringspunkt foran hver kommando, og i tilfelle feil starter en tilbakestilling til det. Denne modusen brukes ikke som standard, siden innstilling av lagringspunkter (selv uten å rulle tilbake til dem) innebærer betydelig overhead.

Fortsettelse.

Kilde: www.habr.com

Legg til en kommentar