MVCC-3. Versioni di stringa

Quindi, abbiamo considerato le questioni relative a isolamento, e fece un ritiro circa organizzare i dati a basso livello. E finalmente siamo arrivati ​​alla parte più interessante: le versioni per archi.

Titolo

Come abbiamo già detto, ogni riga può esistere contemporaneamente in più versioni nel database. Una versione deve essere in qualche modo distinta da un'altra: a questo scopo ciascuna versione ha due contrassegni che determinano il “tempo” di azione di questa versione (xmin e xmax). Tra virgolette - perché non viene utilizzato il tempo in quanto tale, ma uno speciale contatore crescente. E questo contatore è il numero della transazione.

(Come al solito, la realtà è più complicata: il numero della transazione non può aumentare continuamente a causa della limitata capacità di bit del contatore. Ma esamineremo questi dettagli in dettaglio quando arriveremo al congelamento.)

Quando viene creata una riga, xmin viene impostato sul numero di transazione che ha emesso il comando INSERT e xmax viene lasciato vuoto.

Quando una riga viene eliminata, il valore xmax della versione corrente è contrassegnato con il numero della transazione che ha eseguito l'ELIMINA.

Quando una riga viene modificata da un comando UPDATE, vengono effettivamente eseguite due operazioni: DELETE e INSERT. La versione corrente della riga imposta xmax uguale al numero della transazione che ha eseguito l'AGGIORNAMENTO. Viene quindi creata una nuova versione della stessa stringa; il suo valore xmin coincide con il valore xmax della versione precedente.

I campi xmin e xmax sono inclusi nell'intestazione della versione della riga. Oltre a questi campi, l'intestazione ne contiene altri, ad esempio:

  • infomask è una serie di bit che definiscono le proprietà di questa versione. Ce ne sono parecchi; Considereremo gradualmente i principali.
  • ctid è un collegamento alla versione successiva e più recente della stessa riga. Per la versione più recente e più recente di una stringa, il ctid si riferisce a questa versione stessa. Il numero ha la forma (x,y), dove x è il numero di pagina, y è il numero di indice nell'array.
  • bitmap null: contrassegna le colonne di una determinata versione che contengono un valore null (NULL). NULL non è uno dei normali valori del tipo di dati, quindi l'attributo deve essere archiviato separatamente.

Di conseguenza, l'intestazione è piuttosto grande: almeno 23 byte per ciascuna versione della riga e solitamente di più a causa della bitmap NULL. Se la tabella è "stretta" (ovvero contiene poche colonne), il sovraccarico potrebbe richiedere più informazioni utili.

inserire

Diamo uno sguardo più da vicino a come vengono eseguite le operazioni sulle stringhe di basso livello, a partire dall'inserimento.

Per gli esperimenti, creiamo una nuova tabella con due colonne e un indice su una di esse:

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

Inseriamo una riga dopo aver avviato una transazione.

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

Ecco il nostro numero di transazione attuale:

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

Diamo un'occhiata al contenuto della pagina. La funzione heap_page_items dell'estensione pageinspect ti consente di ottenere informazioni sui puntatori e sulle versioni delle righe:

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

Tieni presente che la parola heap in PostgreSQL si riferisce alle tabelle. Questo è un altro strano uso del termine: è noto un mucchio struttura dati, che non ha nulla in comune con la tabella. Qui la parola è usata nel senso di “tutto è messo insieme”, in contrapposizione agli indici ordinati.

La funzione mostra i dati “così come sono”, in un formato difficile da comprendere. Per capirlo, lasceremo solo una parte delle informazioni e le decifreremo:

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

Ecco cosa abbiamo fatto:

  • Aggiunto uno zero al numero di indice per farlo sembrare uguale a t_ctid: (numero di pagina, numero di indice).
  • Decifrato lo stato del puntatore lp_flags. Qui è "normale" - ciò significa che il puntatore si riferisce effettivamente alla versione della stringa. Vedremo più avanti gli altri significati.
  • Di tutti i bit di informazione finora sono state identificate solo due coppie. I bit xmin_comtched e xmin_aborted indicano se la transazione numero xmin è stata confermata (interrotta). Due bit simili si riferiscono al numero di transazione xmax.

Cosa vediamo? Quando inserisci una riga, nella pagina della tabella apparirà un numero di indice 1, che punta alla prima e unica versione della riga.

Nella versione stringa, il campo xmin è riempito con il numero di transazione corrente. La transazione è ancora attiva, quindi entrambi i bit xmin_commit e xmin_aborted non sono impostati.

Il campo ctid della versione della riga si riferisce alla stessa riga. Ciò significa che non esiste una versione più recente.

Il campo xmax è riempito con un numero fittizio 0 perché questa versione della riga non è stata eliminata ed è attuale. Le transazioni non presteranno attenzione a questo numero perché è impostato il bit xmax_aborted.

Facciamo un ulteriore passo avanti verso il miglioramento della leggibilità aggiungendo bit di informazione ai numeri delle transazioni. E creiamo una funzione, poiché avremo bisogno della richiesta più di una volta:

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

In questa forma è molto più chiaro cosa sta succedendo nell'intestazione della versione della riga:

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

Informazioni simili, ma significativamente meno dettagliate, possono essere ottenute dalla tabella stessa, utilizzando le pseudo-colonne xmin e xmax:

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

fissazione

Se una transazione viene completata con successo, devi ricordarne lo stato: nota che è impegnata. Per fare ciò viene utilizzata una struttura chiamata XACT (e prima della versione 10 si chiamava CLOG (commit log) e questo nome può ancora essere trovato in diversi posti).

XACT non è una tabella di catalogo di sistema; questi sono i file nella directory PGDATA/pg_xact. Hanno due bit per ogni transazione: confermata e interrotta, proprio come nell'intestazione della versione della riga. Queste informazioni sono divise in più file esclusivamente per comodità; torneremo su questo argomento quando considereremo il congelamento. E il lavoro con questi file viene eseguito pagina per pagina, come con tutti gli altri.

Pertanto, quando viene eseguita la conferma di una transazione in XACT, il bit impegnato viene impostato per questa transazione. E questo è tutto ciò che accade durante il commit (anche se non stiamo ancora parlando del log di pre-registrazione).

Quando un'altra transazione accede alla pagina della tabella che abbiamo appena visto, dovrà rispondere a diverse domande.

  1. La transazione xmin è stata completata? In caso contrario, la versione creata della stringa non dovrebbe essere visibile.
    Questo controllo viene eseguito esaminando un'altra struttura, che si trova nella memoria condivisa dell'istanza e si chiama ProcArray. Contiene un elenco di tutti i processi attivi e per ciascuno è indicato il numero della transazione corrente (attiva).
  2. Se completato, allora come, impegnandosi o annullando? Se annullato, anche la versione in riga non dovrebbe essere visibile.
    Questo è esattamente lo scopo di XACT. Tuttavia, sebbene le ultime pagine di XACT siano archiviate nei buffer della RAM, è comunque costoso controllare XACT ogni volta. Pertanto, una volta determinato lo stato della transazione, questo viene scritto nei bit xmin_commited e xmin_aborted della versione stringa. Se uno di questi bit è impostato, lo stato della transazione xmin è considerato noto e la transazione successiva non dovrà accedere a XACT.

Perché questi bit non vengono impostati dalla transazione stessa durante l'inserimento? Quando avviene un inserimento, la transazione non sa ancora se avrà successo. E al momento del commit non è più chiaro in quali righe e in quali pagine sono state modificate. Potrebbero esserci molte di queste pagine e memorizzarle non è redditizio. Inoltre, alcune pagine possono essere rimosse dalla cache del buffer sul disco; leggerli di nuovo per modificare i bit rallenterebbe significativamente il commit.

Lo svantaggio del risparmio è che dopo le modifiche, qualsiasi transazione (anche quella che esegue una semplice lettura - SELECT) può iniziare a modificare le pagine di dati nella cache del buffer.

Quindi, sistemiamo il cambiamento.

=> COMMIT;

Non è cambiato nulla nella pagina (ma sappiamo che lo stato della transazione è già registrato in XACT):

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

Ora la transazione che accede per prima alla pagina dovrà determinare lo stato della transazione xmin e scriverlo nei bit di informazione:

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

Rimozione

Quando una riga viene eliminata, il numero della transazione di eliminazione corrente viene scritto nel campo xmax della versione corrente e il bit xmax_aborted viene cancellato.

Si noti che il valore impostato di xmax corrispondente alla transazione attiva funge da blocco di riga. Se un'altra transazione desidera aggiornare o eliminare questa riga, sarà costretta ad attendere il completamento della transazione xmax. Parleremo più approfonditamente del blocco in seguito. Per ora notiamo solo che il numero di blocchi di riga è illimitato. Non occupano spazio nella RAM e le prestazioni del sistema non risentono del loro numero. È vero che le transazioni “lunghe” presentano altri svantaggi, ma ne parleremo più avanti.

Eliminiamo la riga.

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

Vediamo che il numero di transazione è scritto nel campo xmax, ma i bit di informazione non sono impostati:

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

cancellazione

L'annullamento delle modifiche funziona in modo simile al commit, solo che in XACT il bit interrotto è impostato per la transazione. Annullare è veloce quanto impegnarsi. Sebbene il comando si chiami ROLLBACK, le modifiche non vengono annullate: tutto ciò che la transazione è riuscita a modificare nelle pagine dati rimane invariato.

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

Quando si accede alla pagina, lo stato verrà controllato e il bit di suggerimento xmax_aborted verrà impostato sulla versione della riga. Il numero xmax stesso rimane sulla pagina, ma nessuno lo guarderà.

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

Aggiornare

L'aggiornamento funziona come se prima eliminasse la versione corrente della riga e poi ne inserisse una nuova.

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

La query produce una riga (nuova versione):

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

Ma nella pagina vediamo entrambe le versioni:

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

La versione eliminata è contrassegnata con il numero di transazione corrente nel campo xmax. Inoltre, questo valore viene sovrascritto su quello vecchio, poiché la transazione precedente è stata annullata. E il bit xmax_aborted viene cancellato perché lo stato della transazione corrente non è ancora noto.

La prima versione della riga ora si riferisce alla seconda (campo t_ctid) come quella più recente.

Un secondo indice viene visualizzato nella pagina dell'indice e una seconda riga fa riferimento alla seconda versione nella pagina della tabella.

Proprio come nel caso dell'eliminazione, il valore xmax nella prima versione della riga indica che la riga è bloccata.

Bene, completiamo la transazione.

=> COMMIT;

Indici

Finora abbiamo parlato solo delle pagine delle tabelle. Cosa succede all'interno degli indici?

Le informazioni nelle pagine dell'indice variano notevolmente a seconda del tipo specifico di indice. E anche un tipo di indice ha diversi tipi di pagine. Ad esempio, un B-tree ha una pagina di metadati e pagine “normali”.

Tuttavia, la pagina solitamente ha un array di puntatori alle righe e alle righe stesse (proprio come una pagina di tabella). Inoltre, alla fine della pagina c'è spazio per dati speciali.

Le righe negli indici possono anche avere strutture molto diverse a seconda del tipo di indice. Ad esempio, per un B-tree, le righe relative alle pagine foglia contengono il valore della chiave di indicizzazione e un riferimento (ctid) alla riga della tabella corrispondente. In generale, l’indice può essere strutturato in modo completamente diverso.

Il punto più importante è che non esistono versioni di riga negli indici di alcun tipo. Bene, oppure possiamo supporre che ogni riga sia rappresentata esattamente da una versione. In altre parole, non ci sono campi xmin e xmax nell'intestazione della riga dell'indice. Possiamo supporre che i collegamenti dall'indice conducano a tutte le versioni della tabella delle righe, quindi puoi capire quale versione verrà vista dalla transazione solo guardando la tabella. (Come sempre, questa non è tutta la verità. In alcuni casi, la mappa di visibilità può ottimizzare il processo, ma lo vedremo più in dettaglio in seguito.)

Allo stesso tempo, nella pagina indice troviamo i riferimenti ad entrambe le versioni, sia quella attuale che quella vecchia:

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

Transazioni virtuali

In pratica, PostgreSQL utilizza ottimizzazioni che gli consentono di “salvare” i numeri delle transazioni.

Se una transazione legge solo i dati, non ha alcun effetto sulla visibilità delle versioni delle righe. Pertanto, il processo del servizio emette innanzitutto un xid virtuale per la transazione. Il numero è costituito da un ID di processo e da un numero di sequenza.

L'emissione di questo numero non richiede la sincronizzazione tra tutti i processi ed è quindi molto veloce. Conosceremo un altro motivo per utilizzare i numeri virtuali quando parleremo del congelamento.

I numeri virtuali non vengono presi in considerazione in alcun modo negli snapshot dei dati.

In diversi momenti nel sistema potrebbero esserci transazioni virtuali con numeri già utilizzati, e questo è normale. Ma un tale numero non può essere scritto nelle pagine di dati, perché la prossima volta che si accede alla pagina potrebbe perdere ogni significato.

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

Se una transazione inizia a modificare i dati, le viene assegnato un numero di transazione reale e univoco.

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

=> COMMIT;

Transazioni nidificate

Risparmia punti

Definito in SQL salvare punti (savepoint), che permettono di annullare parte di una transazione senza interromperla completamente. Ma questo non rientra nel diagramma sopra, poiché la transazione ha lo stesso stato per tutte le sue modifiche e fisicamente nessun dato viene ripristinato.

Per implementare questa funzionalità, una transazione con un punto di salvataggio viene suddivisa in più transazioni separate transazioni nidificate (sottotransazione), il cui stato può essere gestito separatamente.

Le transazioni nidificate hanno un proprio numero (superiore al numero della transazione principale). Lo stato delle transazioni annidate viene registrato nel solito modo in XACT, ma lo stato finale dipende dallo stato della transazione principale: se viene annullata, verranno annullate anche tutte le transazioni annidate.

Le informazioni sull'annidamento delle transazioni sono archiviate nei file nella directory PGDATA/pg_subtrans. L'accesso ai file avviene tramite buffer nella memoria condivisa dell'istanza, organizzati allo stesso modo dei buffer XACT.

Non confondere le transazioni nidificate con le transazioni autonome. Le transazioni autonome non dipendono in alcun modo l'una dall'altra, ma le transazioni annidate sì. Non ci sono transazioni autonome nel normale PostgreSQL e, forse, è meglio così: sono necessarie molto, molto raramente, e la loro presenza in altri DBMS provoca abusi, di cui poi soffrono tutti.

Svuotiamo la tabella, iniziamo una transazione e inseriamo la riga:

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

Ora inseriamo un punto di salvataggio e inseriamo un'altra riga.

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

Tieni presente che la funzione txid_current() restituisce il numero della transazione principale, non il numero della transazione nidificata.

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

Torniamo al punto di salvataggio e inseriamo la terza riga.

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

Nella pagina continuiamo a vedere la riga aggiunta dalla transazione annidata annullata.

Correggiamo le modifiche.

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

Ora puoi vedere chiaramente che ogni transazione nidificata ha il proprio stato.

Tieni presente che le transazioni nidificate non possono essere utilizzate esplicitamente in SQL, ovvero non puoi avviare una nuova transazione senza completare quella corrente. Questo meccanismo viene attivato implicitamente quando si utilizzano i punti di salvataggio, così come quando si gestiscono le eccezioni PL/pgSQL e in una serie di altri casi più esotici.

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

Errori e atomicità delle operazioni

Cosa succede se si verifica un errore durante l'esecuzione di un'operazione? Ad esempio, in questo modo:

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

C'è stato un errore. Ora la transazione è considerata interrotta e non è consentita alcuna operazione al suo interno:

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

E anche se provi a confermare le modifiche, PostgreSQL segnalerà un'interruzione:

=> COMMIT;
ROLLBACK

Perché una transazione non può continuare dopo un errore? Il fatto è che potrebbe verificarsi un errore in modo tale da ottenere l'accesso a parte delle modifiche: l'atomicità non verrebbe violata nemmeno nella transazione, ma nell'operatore. Come nel nostro esempio, dove l'operatore è riuscito ad aggiornare una riga prima dell'errore:

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

Va detto che psql ha una modalità che consente comunque alla transazione di continuare dopo un errore come se le azioni dell'operatore errato venissero ripristinate.

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

Non è difficile intuire che in questa modalità psql mette effettivamente un punto di salvataggio implicito prima di ogni comando e, in caso di fallimento, avvia un rollback su di esso. Questa modalità non viene utilizzata per impostazione predefinita, poiché l'impostazione dei punti di salvataggio (anche senza ripristinarli) comporta un sovraccarico significativo.

Continua.

Fonte: habr.com

Aggiungi un commento