MVCC-3. Versiuni cu șiruri

Deci, am luat în considerare probleme legate de izolatie, și a făcut o retragere despre organizarea datelor la un nivel scăzut. Și în sfârșit am ajuns la partea cea mai interesantă - versiunile cu șiruri.

Titlu

După cum am spus deja, fiecare rând poate fi prezent simultan în baza de date în mai multe versiuni. O versiune trebuie oarecum distinsă de alta.În acest scop, fiecare versiune are două semne care determină „timpul” de acțiune al acestei versiuni (xmin și xmax). Între ghilimele - pentru că nu timpul ca atare este folosit, ci un numărător special de creștere. Și acest contor este numărul tranzacției.

(Ca de obicei, realitatea este mai complicată: numărul tranzacției nu poate crește tot timpul din cauza capacității limitate de biți a contorului. Dar ne vom uita la aceste detalii în detaliu când vom ajunge la înghețare.)

Când este creat un rând, xmin este setat la numărul tranzacției care a emis comanda INSERT și xmax este lăsat necompletat.

Când un rând este șters, valoarea xmax a versiunii curente este marcată cu numărul tranzacției care a efectuat DELETE.

Atunci când un rând este modificat printr-o comandă UPDATE, sunt efectiv efectuate două operații: DELETE și INSERT. Versiunea curentă a rândului setează xmax egal cu numărul tranzacției care a efectuat UPDATE. O nouă versiune a aceluiași șir este apoi creată; valoarea sa xmin coincide cu valoarea xmax a versiunii anterioare.

Câmpurile xmin și xmax sunt incluse în antetul versiunii de rând. Pe lângă aceste câmpuri, antetul conține și altele, de exemplu:

  • infomask este o serie de biți care definesc proprietățile acestei versiuni. Sunt destul de multe; Pe cele principale le vom lua în considerare treptat.
  • ctid este un link către următoarea versiune mai nouă a aceleiași linii. Pentru cea mai nouă, cea mai actuală versiune a unui șir, ctid se referă la această versiune în sine. Numărul are forma (x,y), unde x este numărul paginii, y este numărul de index din matrice.
  • null bitmap - marchează acele coloane ale unei versiuni date care conțin o valoare nulă (NULL). NULL nu este una dintre valorile normale ale tipului de date, astfel încât atributul trebuie să fie stocat separat.

Ca rezultat, antetul este destul de mare - cel puțin 23 de octeți pentru fiecare versiune a liniei și, de obicei, mai mult din cauza bitmap-ului NULL. Dacă tabelul este „îngust” (adică conține câteva coloane), costul general poate ocupa mai mult decât informațiile utile.

insera

Să aruncăm o privire mai atentă asupra modului în care sunt efectuate operațiunile cu șiruri de nivel scăzut, începând cu inserarea.

Pentru experimente, să creăm un tabel nou cu două coloane și un index pe una dintre ele:

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

Să introducem un rând după începerea unei tranzacții.

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

Iată numărul nostru curent de tranzacție:

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

Să ne uităm la conținutul paginii. Funcția heap_page_items a extensiei pageinspect vă permite să obțineți informații despre indicatori și versiuni de rând:

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

Rețineți că cuvântul heap din PostgreSQL se referă la tabele. Aceasta este o altă utilizare ciudată a termenului - se cunoaște o grămadă structură de date, care nu are nimic în comun cu tabelul. Aici cuvântul este folosit în sensul „totul este aruncat împreună”, spre deosebire de indici ordonați.

Funcția arată datele „ca atare”, într-un format greu de înțeles. Pentru a ne da seama, vom lăsa doar o parte din informații și o vom descifra:

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

Iată ce am făcut:

  • S-a adăugat un zero la numărul de index pentru a-l face să arate la fel cu t_ctid: (numărul paginii, numărul indexului).
  • Am descifrat starea pointerului lp_flags. Aici este „normal” - asta înseamnă că indicatorul se referă de fapt la versiunea șirului. Ne vom uita la alte semnificații mai târziu.
  • Dintre toți biții de informație, doar două perechi au fost identificate până acum. Biții xmin_committed și xmin_aborted indică dacă numărul tranzacției xmin este comis (avortat). Doi biți similari se referă la numărul tranzacției xmax.

Ce vedem? Când inserați un rând, în pagina tabelului va apărea un număr de index 1, indicând prima și singura versiune a rândului.

În versiunea cu șir, câmpul xmin este completat cu numărul tranzacției curente. Tranzacția este încă activă, așa că ambii biții xmin_committed și xmin_aborted nu sunt setați.

Câmpul ctid versiunea rândului se referă la același rând. Aceasta înseamnă că o versiune mai nouă nu există.

Câmpul xmax este completat cu un număr inactiv 0 deoarece această versiune a rândului nu a fost ștearsă și este actuală. Tranzacțiile nu vor acorda atenție acestui număr deoarece bitul xmax_aborted este setat.

Să mai facem un pas spre îmbunătățirea lizibilității prin adăugarea de biți de informații la numerele de tranzacție. Și să creăm o funcție, deoarece vom avea nevoie de cerere de mai multe ori:

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

În această formă, este mult mai clar ce se întâmplă în antetul versiunii de rând:

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

Informații similare, dar semnificativ mai puțin detaliate, pot fi obținute din tabelul în sine, folosind pseudo-coloanele xmin și xmax:

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

fixare

Dacă o tranzacție este finalizată cu succes, trebuie să vă amintiți starea acesteia - rețineți că este comisă. Pentru a face acest lucru, se folosește o structură numită XACT (și înainte de versiunea 10 se numea CLOG (commit log) și acest nume mai poate fi găsit în diferite locuri).

XACT nu este un tabel de catalog de sistem; acestea sunt fișierele din directorul PGDATA/pg_xact. Au doi biți pentru fiecare tranzacție: comis și anulat - la fel ca în antetul versiunii de rând. Aceste informații sunt împărțite în mai multe fișiere doar pentru comoditate; vom reveni la această problemă atunci când ne vom gândi la înghețare. Și lucrul cu aceste fișiere se desfășoară pagină cu pagină, ca și cu toate celelalte.

Deci, atunci când o tranzacție este comisă în XACT, bitul comis este setat pentru această tranzacție. Și asta este tot ce se întâmplă în timpul comiterii (deși nu vorbim încă despre jurnalul de pre-înregistrare).

Când o altă tranzacție accesează pagina tabelului pe care tocmai ne-am uitat, va trebui să răspundă la câteva întrebări.

  1. S-a finalizat tranzacția xmin? Dacă nu, atunci versiunea creată a șirului nu ar trebui să fie vizibilă.
    Această verificare este efectuată analizând o altă structură, care se află în memoria partajată a instanței și se numește ProcArray. Conține o listă a tuturor proceselor active, iar pentru fiecare este indicat numărul tranzacției sale curente (active).
  2. Dacă este finalizat, atunci cum - prin angajare sau anulare? Dacă este anulată, atunci versiunea de rând nu ar trebui să fie vizibilă.
    Exact pentru asta este XACT. Dar, deși ultimele pagini ale XACT sunt stocate în buffere în RAM, este totuși costisitor să verificați XACT de fiecare dată. Prin urmare, odată ce starea tranzacției este determinată, aceasta este scrisă în biții xmin_committed și xmin_aborted ai versiunii șir. Dacă unul dintre acești biți este setat, atunci starea tranzacției xmin este considerată cunoscută și următoarea tranzacție nu va trebui să acceseze XACT.

De ce acești biți nu sunt setați de tranzacția însăși care face inserarea? Când are loc o inserare, tranzacția nu știe încă dacă va reuși. Iar în momentul comiterii nu mai este clar ce rânduri în care pagini au fost schimbate. Pot exista o mulțime de astfel de pagini, iar memorarea lor este neprofitabilă. În plus, unele pagini pot fi evacuate din memoria cache-tampon pe disc; citirea lor din nou pentru a schimba biții ar încetini semnificativ comiterea.

Dezavantajul economiilor este că, după modificări, orice tranzacție (chiar și una care efectuează o simplă citire - SELECT) poate începe să schimbe paginile de date din memoria cache-tampon.

Deci, să reparăm schimbarea.

=> COMMIT;

Nu s-a schimbat nimic pe pagină (dar știm că starea tranzacției este deja înregistrată în XACT):

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

Acum tranzacția care accesează prima pagină va trebui să determine starea tranzacției xmin și să o scrie în biții de informații:

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

Îndepărtare

Când un rând este șters, numărul tranzacției curente de ștergere este scris în câmpul xmax al versiunii curente, iar bitul xmax_aborted este șters.

Rețineți că valoarea setată a xmax corespunzătoare tranzacției active acționează ca o blocare a rândului. Dacă o altă tranzacție dorește să actualizeze sau să ștergă acest rând, va fi forțat să aștepte finalizarea tranzacției xmax. Vom vorbi mai multe despre blocare mai târziu. Deocamdată, observăm doar că numărul de blocări de rând este nelimitat. Nu ocupă spațiu în RAM și performanța sistemului nu suferă de numărul lor. Adevărat, tranzacțiile „lungi” au și alte dezavantaje, dar vom discuta mai târziu.

Să ștergem linia.

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

Vedem că numărul tranzacției este scris în câmpul xmax, dar biții de informații nu sunt setați:

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

anulare

Anularea modificărilor funcționează similar cu comiterea, doar în XACT bitul anulat este setat pentru tranzacție. Anularea este la fel de rapidă ca și angajarea. Deși comanda se numește ROLLBACK, modificările nu sunt anulate: tot ceea ce tranzacția a reușit să schimbe în paginile de date rămâne neschimbat.

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

Când pagina este accesată, starea va fi verificată și bitul indiciu xmax_aborted va fi setat la versiunea de rând. Numărul xmax în sine rămâne pe pagină, dar nimeni nu se va uita la el.

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

actualizare

Actualizarea funcționează ca și cum ar fi șters mai întâi versiunea curentă a rândului și apoi a inserat una nouă.

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

Interogarea produce o linie (versiune nouă):

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

Dar pe pagină vedem ambele versiuni:

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

Versiunea ștearsă este marcată cu numărul tranzacției curente în câmpul xmax. Mai mult, această valoare este scrisă peste cea veche, deoarece tranzacția anterioară a fost anulată. Și bitul xmax_aborted este șters deoarece starea tranzacției curente nu este încă cunoscută.

Prima versiune a liniei se referă acum la a doua (câmpul t_ctid) ca fiind cea mai nouă.

Un al doilea index apare în pagina de index și un al doilea rând face referire la a doua versiune din pagina tabelului.

La fel ca și în cazul ștergerii, valoarea xmax din prima versiune a rândului este o indicație că rândul este blocat.

Ei bine, hai să finalizăm tranzacția.

=> COMMIT;

Indici

Până acum am vorbit doar despre paginile de tabel. Ce se întâmplă în interiorul indicilor?

Informațiile din paginile de index variază foarte mult în funcție de tipul specific de index. Și chiar și un tip de index are diferite tipuri de pagini. De exemplu, un arbore B are o pagină de metadate și pagini „obișnuite”.

Cu toate acestea, pagina are de obicei o serie de pointeri către rânduri și rândurile în sine (la fel ca o pagină de tabel). În plus, la sfârșitul paginii există spațiu pentru date speciale.

Rândurile din indexuri pot avea, de asemenea, structuri foarte diferite în funcție de tipul de index. De exemplu, pentru un arbore B, rândurile aferente paginilor frunze conțin valoarea cheii de indexare și o referință (ctid) la rândul corespunzător al tabelului. În general, indicele poate fi structurat într-un mod complet diferit.

Cel mai important punct este că nu există versiuni de rând în indecși de orice tip. Ei bine, sau putem presupune că fiecare linie este reprezentată de exact o versiune. Cu alte cuvinte, nu există câmpuri xmin și xmax în antetul rândului index. Putem presupune că legăturile din index duc la toate versiunile de tabel ale rândurilor - astfel încât să vă puteți da seama ce versiune va vedea tranzacția doar uitându-vă la tabel. (Ca întotdeauna, acesta nu este întregul adevăr. În unele cazuri, harta vizibilității poate optimiza procesul, dar vom analiza acest lucru mai în detaliu mai târziu.)

În același timp, în pagina de index găsim pointeri către ambele versiuni, atât cea actuală, cât și cea veche:

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

Tranzacții virtuale

În practică, PostgreSQL utilizează optimizări care îi permit să „salveze” numerele de tranzacție.

Dacă o tranzacție citește doar date, nu are niciun efect asupra vizibilității versiunilor de rând. Prin urmare, procesul de service emite mai întâi un xid virtual tranzacției. Numărul constă dintr-un ID de proces și un număr de secvență.

Emiterea acestui număr nu necesită sincronizare între toate procesele și, prin urmare, este foarte rapidă. Ne vom familiariza cu un alt motiv pentru utilizarea numerelor virtuale atunci când vorbim despre înghețare.

Numerele virtuale nu sunt luate în considerare în niciun fel în instantaneele de date.

În momente diferite, pot exista tranzacții virtuale în sistem cu numere care au fost deja utilizate, iar acest lucru este normal. Dar un astfel de număr nu poate fi scris în paginile de date, deoarece la următoarea accesare a paginii poate pierde orice semnificație.

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

Dacă o tranzacție începe să modifice datele, i se atribuie un număr real, unic de tranzacție.

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

=> COMMIT;

Tranzacții imbricate

Salvați puncte

Definit în SQL salva puncte (punct de salvare), care vă permit să anulați o parte a unei tranzacții fără a o întrerupe complet. Dar acest lucru nu se încadrează în diagrama de mai sus, deoarece tranzacția are aceeași stare pentru toate modificările sale și, din punct de vedere fizic, nicio dată nu este anulată.

Pentru a implementa această funcționalitate, o tranzacție cu un punct de salvare este împărțită în mai multe separate tranzacții imbricate (subtranzacție), al cărei statut poate fi gestionat separat.

Tranzacțiile imbricate au propriul număr (mai mare decât numărul tranzacției principale). Starea tranzacțiilor imbricate este înregistrată în mod obișnuit în XACT, dar starea finală depinde de starea tranzacției principale: dacă aceasta este anulată, atunci toate tranzacțiile imbricate sunt și ele anulate.

Informațiile despre imbricarea tranzacțiilor sunt stocate în fișiere din directorul PGDATA/pg_subtrans. Fișierele sunt accesate prin intermediul bufferelor din memoria partajată a instanței, organizate în același mod ca și bufferele XACT.

Nu confunda tranzacțiile imbricate cu tranzacțiile autonome. Tranzacțiile autonome nu depind în niciun fel unele de altele, dar tranzacțiile imbricate depind. Nu există tranzacții autonome în PostgreSQL obișnuit și, poate, în bine: sunt necesare foarte, foarte rar, iar prezența lor în alte DBMS-uri provoacă abuzuri, de care suferă apoi toată lumea.

Să ștergem tabelul, să începem o tranzacție și să introducem rândul:

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

Acum să punem un punct de salvare și să introducem o altă linie.

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

Rețineți că funcția txid_current() returnează numărul tranzacției principale, nu numărul tranzacției imbricate.

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

Să revenim la punctul de salvare și să introducem a treia linie.

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

În pagină vom continua să vedem rândul adăugat de tranzacția imbricată anulată.

Reparăm modificările.

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

Acum puteți vedea clar că fiecare tranzacție imbricată are propriul său statut.

Rețineți că tranzacțiile imbricate nu pot fi utilizate în mod explicit în SQL, adică nu puteți începe o nouă tranzacție fără a finaliza cea curentă. Acest mecanism este activat implicit la utilizarea punctelor de salvare, precum și la manipularea excepțiilor PL/pgSQL și într-o serie de alte cazuri, mai exotice.

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

Erori și atomicitatea operațiilor

Ce se întâmplă dacă apare o eroare în timpul efectuării unei operații? De exemplu, așa:

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

A avut loc o eroare. Acum tranzacția este considerată anulată și nu sunt permise operațiuni în ea:

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

Și chiar dacă încercați să efectuați modificările, PostgreSQL va raporta o întrerupere:

=> COMMIT;
ROLLBACK

De ce nu poate continua o tranzacție după un eșec? Cert este că ar putea apărea o eroare în așa fel încât să obținem acces la o parte din modificări - nici măcar atomicitatea tranzacției, dar operatorul ar fi încălcat. Ca și în exemplul nostru, în care operatorul a reușit să actualizeze o linie înainte de eroare:

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

Trebuie spus că psql are un mod care permite în continuare tranzacția să continue după un eșec, ca și cum acțiunile operatorului eronat ar fi fost anulate.

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

Nu este greu de ghicit că, în acest mod, psql pune de fapt un punct de salvare implicit înaintea fiecărei comenzi și, în caz de eșec, inițiază un rollback la acesta. Acest mod nu este utilizat în mod implicit, deoarece setarea punctelor de salvare (chiar fără a reveni la ele) implică o suprasarcină semnificativă.

Continuare.

Sursa: www.habr.com

Adauga un comentariu