MVCC-3. String versioner

Så vi har overvejet spørgsmål relateret til isolering, og lavede et tilbagetog om at organisere data på et lavt niveau. Og endelig kom vi til den mest interessante del - strengversionerne.

Titel

Som vi allerede har sagt, kan hver række samtidig eksistere i flere versioner i databasen. En version skal på en eller anden måde adskilles fra en anden. Til dette formål har hver version to mærker, der bestemmer "tidspunktet" for denne versions handling (xmin og xmax). I anførselstegn - for det er ikke tiden som sådan, der bruges, men en særlig stigende tæller. Og denne tæller er transaktionsnummeret.

(Som sædvanligt er virkeligheden mere kompliceret: transaktionsnummeret kan ikke stige hele tiden på grund af tællerens begrænsede bitkapacitet. Men vi vil se nærmere på disse detaljer, når vi kommer til frysepunktet.)

Når en række oprettes, sættes xmin til det transaktionsnummer, der udstedte INSERT-kommandoen, og xmax efterlades tom.

Når en række slettes, markeres xmax-værdien for den aktuelle version med nummeret på den transaktion, der udførte SLET.

Når en række ændres af en UPDATE-kommando, udføres der faktisk to operationer: DELETE og INSERT. Den aktuelle version af rækken sætter xmax lig med nummeret på den transaktion, der udførte OPDATERING. En ny version af den samme streng oprettes derefter; dens xmin-værdi falder sammen med xmax-værdien i den tidligere version.

Felterne xmin og xmax er inkluderet i rækkeversionens overskrift. Ud over disse felter indeholder overskriften andre, for eksempel:

  • infomask er en række bits, der definerer egenskaberne for denne version. Dem er der ret mange af; Vi vil gradvist overveje de vigtigste.
  • ctid er et link til den næste, nyere version af samme linje. For den nyeste, mest aktuelle version af en streng, refererer ctid til denne version selv. Tallet har formen (x,y), hvor x er sidetallet, y er indeksnummeret i arrayet.
  • null bitmap - Markerer de kolonner i en given version, der indeholder en nulværdi (NULL). NULL er ikke en af ​​de normale datatypeværdier, så attributten skal gemmes separat.

Som et resultat er headeren ret stor - mindst 23 bytes for hver version af linjen, og normalt mere på grund af NULL bitmap. Hvis tabellen er "smal" (dvs. indeholder få kolonner), kan overhead fylde mere end de nyttige oplysninger.

indsætte

Lad os se nærmere på, hvordan strengoperationer på lavt niveau udføres, begyndende med indsættelse.

For eksperimenter, lad os oprette en ny tabel med to kolonner og et indeks på en af ​​dem:

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

Lad os indsætte en række efter at have startet en transaktion.

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

Her er vores aktuelle transaktionsnummer:

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

Lad os se på indholdet af siden. Heap_page_items-funktionen i pageinspect-udvidelsen giver dig mulighed for at få oplysninger om pointere og rækkeversioner:

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

Bemærk, at ordet heap i PostgreSQL refererer til tabeller. Dette er en anden mærkelig brug af udtrykket - en bunke er kendt datastruktur, som ikke har noget tilfælles med bordet. Her bruges ordet i betydningen "alt er smidt sammen", i modsætning til ordnede indekser.

Funktionen viser data "som de er", i et format, der er svært at forstå. For at finde ud af det, vil vi kun efterlade en del af informationen og dechifrere 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 hvad vi gjorde:

  • Tilføjet et nul til indeksnummeret for at få det til at se det samme ud som t_ctid: (sidenummer, indeksnummer).
  • Dechiffrerede tilstanden for lp_flags-markøren. Her er det "normalt" - det betyder, at markøren faktisk refererer til versionen af ​​strengen. Vi vil se på andre betydninger senere.
  • Af alle informationsbits er kun to par blevet identificeret indtil videre. Bittene xmin_committed og xmin_aborted angiver, om transaktionsnummeret xmin er begået (afbrudt). To lignende bits henviser til transaktionsnummer xmax.

Hvad ser vi? Når du indsætter en række, vises et indeksnummer 1 på tabelsiden, der peger på den første og eneste version af rækken.

I strengversionen er xmin-feltet udfyldt med det aktuelle transaktionsnummer. Transaktionen er stadig aktiv, så både xmin_committed og xmin_aborted bits er ikke indstillet.

Rækkeversionens ctid-felt henviser til den samme række. Det betyder, at en nyere version ikke eksisterer.

Xmax-feltet er udfyldt med et dummy-nummer 0, fordi denne version af rækken ikke er blevet slettet og er aktuel. Transaktioner vil ikke være opmærksomme på dette nummer, fordi xmax_aborted bit er indstillet.

Lad os tage endnu et skridt i retning af at forbedre læsbarheden ved at tilføje informationsbits til transaktionsnumre. Og lad os oprette en funktion, da vi har brug for anmodningen mere end é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 denne form er det meget tydeligere, hvad der foregår i overskriften på rækkeversionen:

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

Lignende, men væsentligt mindre detaljerede, oplysninger kan fås fra selve tabellen ved hjælp af pseudo-kolonner xmin og xmax:

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

Fixering

Hvis en transaktion er gennemført, skal du huske dens status - bemærk, at den er forpligtet. For at gøre dette bruges en struktur kaldet XACT (og før version 10 hed det CLOG (commit log) og dette navn kan stadig findes forskellige steder).

XACT er ikke en systemkatalogtabel; disse er filerne i mappen PGDATA/pg_xact. De har to bits for hver transaktion: begået og afbrudt - ligesom i rækkeversionens header. Disse oplysninger er opdelt i flere filer udelukkende for nemheds skyld; vi vender tilbage til dette problem, når vi overvejer at fryse. Og arbejdet med disse filer udføres side for side, ligesom med alle andre.

Så når en transaktion er forpligtet i XACT, indstilles den forpligtede bit for denne transaktion. Og dette er alt, hvad der sker under commit (selvom vi ikke taler om pre-recording-loggen endnu).

Når en anden transaktion får adgang til den tabelside, vi lige har set på, skal den besvare flere spørgsmål.

  1. Er xmin-transaktionen gennemført? Hvis ikke, bør den oprettede version af strengen ikke være synlig.
    Denne kontrol udføres ved at se på en anden struktur, som er placeret i instansens delte hukommelse og kaldes ProcArray. Den indeholder en liste over alle aktive processer, og for hver enkelt er nummeret på dens aktuelle (aktive) transaktion angivet.
  2. Hvis fuldført, hvordan - ved at binde eller annullere? Hvis det annulleres, bør rækkeversionen heller ikke være synlig.
    Det er præcis, hvad XACT er til. Men selvom de sidste sider af XACT er gemt i buffere i RAM, er det stadig dyrt at tjekke XACT hver gang. Derfor, når transaktionsstatus er bestemt, skrives den til xmin_committed og xmin_aborted bits af strengversionen. Hvis en af ​​disse bits er indstillet, betragtes status for transaktionen xmin som kendt, og den næste transaktion behøver ikke at få adgang til XACT.

Hvorfor indstilles disse bits ikke af selve transaktionen, der udfører indsættelsen? Når en indsættelse sker, ved transaktionen endnu ikke, om den vil lykkes. Og på tidspunktet for committing er det ikke længere klart, hvilke linjer i hvilke sider der blev ændret. Der kan være mange sådanne sider, og det er urentabelt at huske dem. Derudover kan nogle sider fjernes fra buffercachen til disken; at læse dem igen for at ændre bits ville bremse commit betydeligt.

Ulempen ved besparelserne er, at enhver transaktion (selv en der udfører en simpel læsning - SELECT) efter ændringer kan begynde at ændre datasider i buffercachen.

Så lad os rette ændringen.

=> COMMIT;

Intet er ændret på siden (men vi ved, at transaktionsstatus allerede er registreret 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 skal transaktionen, der først får adgang til siden, bestemme xmin-transaktionsstatussen og skrive den til informationsbittene:

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

Fjernelse

Når en række slettes, skrives nummeret på den aktuelle slettetransaktion til xmax-feltet i den aktuelle version, og xmax_aborted-bitten slettes.

Bemærk, at den indstillede værdi af xmax svarende til den aktive transaktion fungerer som en rækkelås. Hvis en anden transaktion ønsker at opdatere eller slette denne række, vil den blive tvunget til at vente på, at transaktionen xmax er fuldført. Vi vil tale mere om blokering senere. Indtil videre bemærker vi blot, at antallet af rækkelåse er ubegrænset. De optager ikke plads i RAM, og systemets ydeevne lider ikke under deres antal. Sandt nok har "lange" transaktioner andre ulemper, men mere om det senere.

Lad os slette linjen.

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

Vi ser, at transaktionsnummeret er skrevet i xmax-feltet, men informationsbittene er ikke indstillet:

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

Annullering

Afbrydelse af ændringer fungerer på samme måde som committing, kun i XACT er den afbrudte bit indstillet til transaktionen. At fortryde er lige så hurtigt som at forpligte sig. Selvom kommandoen hedder ROLLBACK, rulles ændringer ikke tilbage: alt, hvad transaktionen formåede at ændre på datasiderne, forbliver uændret.

=> 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 er åbnet, vil status blive kontrolleret, og xmax_aborted hint bit vil blive sat til rækkeversionen. Selve xmax-tallet forbliver 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)

Opdater

Opdateringen fungerer, som om den først slettede den aktuelle version af rækken og derefter indsatte en ny.

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

Forespørgslen producerer én linje (ny version):

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

Men på siden ser vi begge versioner:

=> 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 version er markeret med det aktuelle transaktionsnummer i xmax-feltet. Desuden er denne værdi skrevet over den gamle, da den tidligere transaktion blev annulleret. Og xmax_aborted-bitten ryddes, fordi status for den aktuelle transaktion endnu ikke er kendt.

Den første version af linjen refererer nu til den anden (t_ctid-felt) som den nyere.

Et andet indeks vises på indekssiden, og en anden række refererer til den anden version på tabelsiden.

Ligesom ved sletning er xmax-værdien i den første version af rækken en indikation af, at rækken er låst.

Nå, lad os fuldføre transaktionen.

=> COMMIT;

Indekser

Indtil videre har vi kun talt om tabelsider. Hvad sker der inde i indekserne?

Oplysningerne på indekssider varierer meget afhængigt af den specifikke type indeks. Og selv én type indeks har forskellige typer sider. For eksempel har et B-træ en metadataside og "almindelige" sider.

Siden har dog normalt en række pointere til rækkerne og selve rækkerne (ligesom en tabelside). Derudover er der i slutningen af ​​siden plads til særlige data.

Rækker i indekser kan også have meget forskellige strukturer afhængigt af typen af ​​indeks. For et B-træ indeholder de rækker, der er relateret til bladsider, f.eks. indekseringsnøgleværdien og en reference (ctid) til den tilsvarende tabelrække. Generelt kan indekset være struktureret på en helt anden måde.

Det vigtigste punkt er, at der ikke er rækkeversioner i indekser af nogen art. Nå, eller vi kan antage, at hver linje er repræsenteret af nøjagtig én version. Med andre ord er der ingen xmin- og xmax-felter i indeksrækkeoverskriften. Vi kan antage, at links fra indekset fører til alle tabelversioner af rækkerne - så du kan kun finde ud af, hvilken version transaktionen vil se ved at se på tabellen. (Det er som altid ikke hele sandheden. I nogle tilfælde kan synlighedskortet optimere processen, men det vil vi se nærmere på senere.)

Samtidig finder vi på indekssiden pointere til begge versioner, både den nuvæ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 transaktioner

I praksis bruger PostgreSQL optimeringer, der gør det muligt at "gemme" transaktionsnumre.

Hvis en transaktion kun læser data, har det ingen indflydelse på rækkeversionernes synlighed. Derfor udsteder serviceprocessen først en virtuel xid til transaktionen. Nummeret består af et proces-id og et sekvensnummer.

Udstedelse af dette nummer kræver ikke synkronisering mellem alle processer og er derfor meget hurtig. Vi vil stifte bekendtskab med en anden grund til at bruge virtuelle tal, når vi taler om frysning.

Virtuelle tal tages ikke i betragtning på nogen måde i datasnapshots.

På forskellige tidspunkter kan der meget vel være virtuelle transaktioner i systemet med numre, der allerede er brugt, og det er normalt. Men sådan et tal kan ikke skrives ind i datasider, for næste gang siden tilgås kan det miste al mening.

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

Hvis en transaktion begynder at ændre data, får den et reelt, unikt transaktionsnummer.

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

=> COMMIT;

Indlejrede transaktioner

Gem point

Defineret i SQL spare point (savepoint), som giver dig mulighed for at annullere en del af en transaktion uden at afbryde den helt. Men dette passer ikke ind i ovenstående diagram, da transaktionen har samme status for alle dens ændringer, og fysisk ingen data rulles tilbage.

For at implementere denne funktionalitet er en transaktion med et lagringspunkt opdelt i flere separate indlejrede transaktioner (undertransaktion), hvis status kan administreres separat.

Indlejrede transaktioner har deres eget nummer (højere end nummeret på hovedtransaktionen). Status for indlejrede transaktioner registreres på sædvanlig måde i XACT, men den endelige status afhænger af status for hovedtransaktionen: hvis den annulleres, annulleres alle indlejrede transaktioner også.

Oplysninger om indlejring af transaktioner gemmes i filer i mappen PGDATA/pg_subtrans. Filer tilgås via buffere i instansens delte hukommelse, organiseret på samme måde som XACT-buffere.

Forveksle ikke indlejrede transaktioner med autonome transaktioner. Autonome transaktioner er ikke afhængige af hinanden på nogen måde, men indlejrede transaktioner gør. Der er ingen autonome transaktioner i almindelig PostgreSQL, og måske i bedste fald: de er nødvendige meget, meget sjældent, og deres tilstedeværelse i andre DBMS'er fremkalder misbrug, som alle så lider af.

Lad os rydde tabellen, starte en transaktion og indsætte rækken:

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

Lad os nu sætte et gemmepunkt og indsætte en anden linje.

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

Bemærk, at funktionen txid_current() returnerer hovedtransaktionsnummeret, ikke det indlejrede transaktionsnummer.

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

Lad os rulle tilbage til lagringspunktet og indsætte den tredje linje.

=> 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 fortsætter vi med at se rækken tilføjet af den annullerede indlejrede transaktion.

Vi retter ændringerne.

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

Nu kan du tydeligt se, at hver indlejret transaktion har sin egen status.

Bemærk, at indlejrede transaktioner ikke kan bruges eksplicit i SQL, det vil sige, du kan ikke starte en ny transaktion uden at fuldføre den nuværende. Denne mekanisme aktiveres implicit ved brug af savepoints, såvel som ved håndtering af PL/pgSQL-undtagelser og i en række andre, mere eksotiske tilfælde.

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

Fejl og atomicitet af operationer

Hvad sker der, hvis der opstår en fejl under udførelse af en handling? For eksempel sådan her:

=> 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 opstod en fejl. Nu betragtes transaktionen som afbrudt, og ingen operationer er tilladt i den:

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

Og selvom du forsøger at foretage ændringerne, vil PostgreSQL rapportere en afbrydelse:

=> COMMIT;
ROLLBACK

Hvorfor kan en transaktion ikke fortsætte efter en fejl? Faktum er, at en fejl kunne opstå på en sådan måde, at vi ville få adgang til en del af ændringerne - atomiciteten af ​​ikke engang transaktionen, men operatøren ville blive krænket. Som i vores eksempel, hvor operatøren formåede at opdatere en linje før fejlen:

=> 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 skal siges, at psql har en tilstand, der stadig tillader transaktionen at fortsætte efter en fejl, som om handlingerne fra den fejlagtige operatør blev rullet tilbage.

=> 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 svært at gætte, at i denne tilstand sætter psql faktisk et implicit lagringspunkt før hver kommando, og i tilfælde af fejl starter en tilbagerulning til det. Denne tilstand bruges ikke som standard, da indstilling af sparepunkter (selv uden at rulle tilbage til dem) involverer betydelige overhead.

Fortsættelse.

Kilde: www.habr.com

Tilføj en kommentar