Quindi, abbiamo esaminato questioni riguardanti , e abbiamo fatto una digressione su . E finalmente siamo arrivati alla parte più interessante: le versioni delle righe.
Intestazione
Come abbiamo già detto, ogni riga può essere presente nel database in più versioni contemporaneamente. È necessario distinguere un versione dall'altra. A tale scopo, ogni versione ha due marcatori che definiscono il «tempo» di validità di quella versione (xmin e xmax). Tra virgolette — perché non si utilizza effettivamente il tempo, ma un contatore speciale in crescita. E questo contatore è il numero della transazione.
(Come al solito, in realtà è tutto più complicato: il numero delle transazioni non può crescere continuamente a causa della limitata larghezza del contatore. Ma esamineremo questi dettagli in dettaglio quando parleremo del congelamento.)
Quando una riga viene creata, il valore xmin viene impostato sul numero della transazione che ha eseguito il comando INSERT, mentre xmax non viene popolato.
Quando una riga viene eliminata, il valore xmax dell'attuale versione viene contrassegnato con il numero della transazione che ha eseguito il DELETE.
Quando una riga viene modificata dal comando UPDATE, vengono effettivamente eseguite due operazioni: DELETE e INSERT. Nella versione attuale, la riga ha un xmax impostato al numero della transazione che ha eseguito l'UPDATE. Viene quindi creata una nuova versione della stessa riga; il valore di xmin coincide con quello di xmax della versione precedente.
I campi xmin e xmax fanno parte dell'intestazione della versione della riga. Oltre a questi campi, l'intestazione contiene anche altri, come:
- infomask — una serie di bit che definiscono le proprietà di questa versione. Ce ne sono abbastanza; nei prossimi approfondimenti esamineremo gradualmente i principali.
- ctid — un riferimento alla successiva versione più recente della stessa riga. Nella versione più recente e attuale, ctid si riferisce a questa versione. Il numero ha il formato (x,y), dove x è il numero della pagina e y è il numero di ordine del puntatore nell'array.
- una mappa di bit dei valori indefiniti — indica le colonne di questa versione che contengono un valore indefinito (NULL). NULL non è uno dei normali valori dei tipi di dati, quindi è necessario memorizzare il segnale separatamente.
Di conseguenza, il titolo risulta piuttosto lungo: almeno 23 byte per ogni versione della riga, e di solito di più a causa della bitmap dei NULL. Se la tabella è 'stretta' (cioè contiene poche colonne), le spese generali possono occupare più spazio delle informazioni utili.
Inserimento
Esaminiamo più in dettaglio come vengono eseguite le operazioni sulle righe a basso livello, iniziando dall'inserimento.
Per le sperimentazioni, 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, iniziando prima la transazione.
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
Ecco il numero della nostra attuale transazione:
=> SELECT txid_current();
txid_current
--------------
3664
(1 row)
Diamo un'occhiata al contenuto della pagina. La funzione heap_page_items dell'estensione pageinspect permette di ottenere informazioni sui puntatori e le 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
Nota che con il termine heap (coda) in PostgreSQL si intendono le tabelle. Questa è un'altra strana accezione del termine: la coda è una struttura dati nota La funzione mostra i dati «così come sono», in un formato difficile da interpretare. Per chiarire, lasceremo solo una parte delle informazioni e la 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:
Abbiamo aggiunto uno zero al numero del puntatore per portarlo allo stesso formato di t_ctid: (numero della pagina, numero del puntatore).
- Abbiamo aggiunto uno zero al numero del puntatore per renderlo simile a t_ctid: (numero di pagina, numero del puntatore).
- Abbiamo decifrato lo stato del puntatore lp_flags. Qui è "normal" — significa che il puntatore si riferisce effettivamente alla versione della riga. Altri valori saranno esaminati in seguito.
- Finora abbiamo isolato solo due coppie di bit informativi. I bit xmin_committed e xmin_aborted indicano se la transazione con numero xmin è stata confermata (o annullata). Due bit analoghi si riferiscono alla transazione con numero xmax.
Cosa vediamo quindi? Quando inseriamo una riga nella pagina tabellare, apparirà un puntatore con numero 1, che si riferisce alla prima e unica versione della riga.
Nella versione della riga, il campo xmin è compilato con il numero della transazione attuale. La transazione è ancora attiva, quindi entrambi i bit xmin_committed e xmin_aborted non sono impostati.
Il campo ctid della versione della riga si riferisce a questa stessa riga. Ciò significa che non esiste una versione più recente.
Il campo xmax è compilato con il numero fittizio 0, poiché questa versione della riga non è stata eliminata ed è attuale. Le transazioni non terranno conto di questo numero, poiché il bit xmax_aborted è impostato.
Facciamo un altro passo per migliorare la leggibilità, aggiungendo i bit informativi ai numeri delle transazioni. E creiamo una funzione, poiché avremo bisogno della query ancora una volta:
=> CREARE FUNZIONE heap_page(relname text, pageno integer)
RETURNS TABLE(ctid tid, stato text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno, lp)::text::tid AS ctid,
CASE lp_flags
WHEN 0 THEN 'non utilizzato'
WHEN 1 THEN 'normale'
WHEN 2 THEN 'redirect a '||lp_off
WHEN 3 THEN 'morto'
END AS stato,
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;
$$ LINGUAGGIO SQL;
In questo modo è notevolmente più chiaro cosa sta accadendo nell'intestazione della versione della riga:
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normale | 3664 | 0 (a) | (0,1)
(1 riga)
Informazioni simili, ma sostanzialmente meno dettagliate, possono essere ottenute anche dalla tabella stessa, utilizzando le pseudocolonne xmin e xmax:
=> SELEZIONA xmin, xmax, * DA t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 riga)
Completamento
Al termine dell'operazione di transazione, è necessario ricordare il suo stato — segnare che è stata confermata. A tal fine si utilizza una struttura chiamata XACT (precedentemente, fino alla versione 10, si chiamava CLOG (commit log) e questo nome può ancora apparire in vari contesti).
XACT — non è una tabella del catalogo di sistema; sono file nella directory PGDATA/pg_xact. In essi, per ogni transazione, vengono riservati due bit: committed e aborted — proprio come nell'intestazione della versione della riga. Queste informazioni sono suddivise in diversi file esclusivamente per comodità; torneremo su questo punto quando discuteremo del frozen. La gestione di questi file avviene pagina per pagina, proprio come per tutti gli altri.
Quindi, durante la conferma di una transazione in XACT, viene impostato il bit committed per quella transazione. Ed è tutto ciò che accade durante la conferma (anche se per ora non parliamo del log write-ahead).
Quando un'altra transazione accede alla pagina della tabella che abbiamo appena esaminato, deve rispondere a alcune domande.
- La transazione xmin è terminata? Se no, la versione della riga creata non dovrebbe essere visibile.
Tale verifica viene effettuata consultando un'altra struttura, che si trova nella memoria condivisa dell'istanza e viene chiamata ProcArray. Essa contiene un elenco di tutti i processi attivi e per ciascuno di essi viene indicato il numero della sua transazione attuale (attiva). - Se è terminata, come — con una conferma o un annullamento? Se con un annullamento, allora anche la versione della riga non dovrebbe essere visibile.
Proprio per questo è necessario XACT. Tuttavia, anche se le ultime pagine di XACT vengono memorizzate nei buffer in memoria, controllare XACT ogni volta è comunque oneroso. Pertanto, lo stato della transazione, una volta determinato, viene registrato nei bit xmin_committed e xmin_aborted della versione della riga. Se uno di questi bit è impostato, lo stato della transazione xmin è considerato noto e la successiva transazione non dovrà più riferirsi a XACT.
Perché questi bit non vengono impostati dalla transazione che esegue l'inserimento? Durante l'inserimento, la transazione non sa ancora se avrà successo. E al momento della conferma, non è chiaro quali righe in quali pagine siano state modificate. Ci possono essere molte di queste pagine e memorizzarle non è conveniente. Inoltre, alcune pagine possono essere espulse dalla cache del buffer su disco; leggerle di nuovo per modificare i bit significherebbe rallentare notevolmente la conferma.
Il rovescio dell'economia è che, dopo le modifiche, ogni transazione (anche quelle che eseguono una semplice lettura — SELECT) può iniziare a modificare le pagine dei dati nel cache di buffer.
Quindi, registriamo la modifica.
=> COMMIT;
Nella pagina nulla è cambiato (ma sappiamo che lo stato della transazione è già stato registrato in XACT):
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normale | 3664 | 0 (a) | (0,1)
(1 riga)
Ora la transazione che per prima ha interagito con la pagina dovrà determinare lo stato della transazione xmin e registrarlo nei bit informativi:
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 row)
=> SELEZIONA * DA heap_page('t', 0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 row)
Eliminazione
Quando si elimina una riga, il numero della transazione di eliminazione corrente viene registrato nel campo xmax della versione attuale, mentre il bit xmax_aborted viene azzerato.
Si noti che il valore xmax impostato, corrispondente alla transazione attiva, funge da blocco della riga. Se un'altra transazione cerca di aggiornare o eliminare questa riga, dovrà attendere il completamento della transazione xmax. Parleremo di blocchi più avanti. Per ora, si noti solo che il numero di blocchi delle righe non è soggetto a limitazioni. Non occupano spazio nella memoria e le prestazioni del sistema non sono influenzate dal loro numero. Tuttavia, le transazioni "lunghe" presentano altri svantaggi, ma ne parleremo più tardi.
Elimina la riga.
=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
txid_current
--------------
3665
(1 riga)
Vediamo che il numero della transazione è stato registrato nel campo xmax, ma i bit informativi non sono impostati:
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normale | 3664 (c) | 3665 | (0,1)
(1 riga)
Annulla
L'annullamento delle modifiche funziona in modo analogo all'impegno, solo che per la transazione viene impostato il bit aborted nell'XACT. L'annullamento avviene altrettanto rapidamente quanto l'impegno. Sebbene il comando sia chiamato ROLLBACK, non ci sono effettivi rollback delle modifiche: tutto ciò che la transazione è riuscita a modificare nelle pagine dati rimane invariato.
=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normale | 3664 (c) | 3665 | (0,1)
(1 riga)
Quando accedi a una pagina, verrà verificato lo stato e il bit di suggerimento xmax_aborted verrà impostato sulla versione della riga. Il numero xmax rimane sulla pagina, ma nessuno lo osserverà più.
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 row)
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normale | 3664 (c) | 3665 (a) | (0,1)
(1 riga)
Aggiornamento
L'aggiornamento funziona come se prima fosse stata eseguita la cancellazione della versione attuale della riga e poi l'inserimento di una nuova.
=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
txid_current
--------------
3666
(1 riga)
La query restituisce una riga (nuova versione):
=> SELECT * FROM t;
id | s
----+-----
1 | BAR
(1 riga)
Ma nella pagina vediamo entrambe le versioni:
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normale | 3664 (c) | 3666 | (0,2)
(0,2) | normale | 3666 | 0 (a) | (0,2)
(2 righe)
La versione rimossa è contrassegnata con il numero dell'attuale transazione nel campo xmax. Inoltre, questo valore è stato scritto sopra il vecchio, poiché la transazione precedente è stata annullata. E il bit xmax_aborted è stato azzerato, poiché lo stato dell'attuale transazione è ancora sconosciuto.
La prima versione della riga ora fa riferimento alla seconda (campo t_ctid), come se fosse più recente.
Nella pagina dell'indice appare un secondo puntatore e una seconda riga, che fa riferimento alla seconda versione nella pagina tabellare.
Proprio come nella rimozione, il valore xmax nella prima versione della riga funge da segnale che la riga è bloccata.
Ora concludiamo la transazione.
=> COMMIT;
Indici
Fino ad ora abbiamo parlato solo delle pagine tabellari. Cosa succede all'interno degli indici?
Le informazioni nelle pagine degli indici dipendono fortemente dal tipo specifico di indice. Anche all'interno dello stesso tipo di indice possono esistere diversi tipi di pagine. Ad esempio, un B-albero ha una pagina di metadata e 'pagine normali'.
Tuttavia, normalmente una pagina contiene un array di puntatori a righe e le stesse righe (proprio come nella pagina tabellare). Inoltre, alla fine della pagina viene riservato 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-albero, le righe relative alle pagine foglia contengono il valore della chiave di indicizzazione e un riferimento (ctid) alla corrispondente riga della tabella. In generale, un indice può essere strutturato in modo completamente diverso.
Il punto più importante è che negli indici di qualsiasi tipo non ci sono versioni delle righe. O si può dire che ogni riga è rappresentata esattamente da una versione. In altre parole, nella intestazione della riga dell'indice non ci sono campi xmin e xmax. Si può considerare che i riferimenti dagli indici puntano a tutte le versioni della tabella delle righe — quindi capire quale versione vedrà una transazione è possibile solo guardando nella tabella. (Come al solito, questa non è tutta la verità. In alcuni casi, la mappa di visibilità consente di ottimizzare il processo, ma ne parleremo più dettagliatamente in seguito.)
In questa pagina dell'indice troviamo riferimenti a entrambe le versioni, sia quella attuale che quella obsoleta:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 righe)
Transazioni virtuali
Nella pratica, PostgreSQL utilizza un'ottimizzazione che permette di "risparmiare" numeri di transazione.
Se una transazione legge solo i dati, non influisce sulla visibilità delle versioni delle righe. Pertanto, all'inizio, il processo di servizio assegna alla transazione un numero virtuale (virtual xid). Il numero è composto da un identificatore di processo e da un numero sequenziale.
L'assegnazione di questo numero non richiede la sincronizzazione tra tutti i processi e quindi avviene molto rapidamente. Un'altra ragione per utilizzare numeri virtuali la scopriremo quando parleremo del congelamento.
I numeri virtuali non vengono considerati nelle istantanee dei dati.
In momenti diversi, nel sistema possono trovarsi transazioni virtuali con numeri già utilizzati, e questo è normale. Tuttavia, un numero del genere non può essere registrato nelle pagine dei dati, poiché alla successiva consultazione della pagina potrebbe perdere completamente di significato.
=> BEGIN;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 row)
Se la transazione inizia a modificare i dati, le viene assegnato un vero numero di transazione unico.
=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 row)
=> COMMIT;
Transazioni nidificate
Punti di salvataggio
Definiti in SQL punti di salvataggio (savepoint), che consentono di annullare una parte delle operazioni della transazione senza interromperla completamente. Tuttavia, ciò non si inserisce nello schema sopra riportato, poiché lo stato della transazione è unico per tutte le sue modifiche, mentre fisicamente nessun dato viene ripristinato.
Per implementare tale funzionalità, la transazione con punto di salvataggio viene suddivisa in più sotto-transazioni (subtransaction), il cui stato può essere gestito separatamente.
Le sotto-transazioni hanno il proprio numero (maggiore del numero della transazione principale). Lo stato delle sotto-transazioni viene registrato in modo normale in XACT, tuttavia lo stato finale dipende dallo stato della transazione principale: se questa viene annullata, anche tutte le sotto-transazioni vengono annullate.
Le informazioni sulla nidificazione delle transazioni sono memorizzate in file nella directory PGDATA/pg_subtrans. L'accesso ai file avviene tramite i buffer nella memoria condivisa dell'istanza, organizzati allo stesso modo dei buffer XACT.
Non confondete le transazioni nidificate con le transazioni autonome. Le transazioni autonome non dipendono l'una dall'altra, mentre le nidificate sì. Non ci sono transazioni autonome nel PostgreSQL standard e, probabilmente, è meglio così: in effetto sono necessarie solo molto raramente, e la loro presenza in altri DBMS porta a malintesi che poi danneggiano tutti.
Ripuliamo la tabella, iniziamo una transazione e inseriamo una riga:
=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 riga)
=> SELEZIONA xmin, xmax, * DA t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 riga)
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normale | 3669 | 0 (a) | (0,1)
(1 riga)
Impostiamo ora un punto di salvataggio e aggiungiamo un'altra riga.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 riga)
Notate che la funzione txid_current() restituisce il numero della transazione principale, non di quella nidificata.
=> SELEZIONA xmin, xmax, * DA t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 righe)
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normale | 3669 | 0 (a) | (0,1)
(0,2) | normale | 3670 | 0 (a) | (0,2)
(2 righe)
Ripristiniamo il punto di salvataggio e inseriamo una 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 righe)
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normale | 3669 | 0 (a) | (0,1)
(0,2) | normale | 3670 (a) | 0 (a) | (0,2)
(0,3) | normale | 3671 | 0 (a) | (0,3)
(3 righe)
Nella pagina continuiamo a vedere la riga aggiunta dalla transazione annullata.
Registriamo le modifiche.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 righe)
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normale | 3669 (c) | 0 (a) | (0,1)
(0,2) | normale | 3670 (a) | 0 (a) | (0,2)
(0,3) | normale | 3671 (c) | 0 (a) | (0,3)
(3 righe)
Ora è chiaro che ogni transazione annidata ha il proprio stato.
Notiamo che le transazioni annidate non possono essere utilizzate esplicitamente in SQL, ovvero non è possibile avviare una nuova transazione senza completare quella attuale. Questo meccanismo viene attivato implicitamente utilizzando i punti di salvataggio, e anche durante la gestione delle eccezioni PL/pgSQL e in altri casi più esotici.
=> BEGIN;
BEGIN
=> BEGIN;
AVVISO: esiste già una transazione in corso
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
AVVISO: non c'è alcuna transazione in corso
COMMIT
Errori e atomicità delle operazioni
Cosa succede se si verifica un errore durante l'esecuzione di un'operazione? Ad esempio, così:
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 righe)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERRORE: divisione per zero
Si è verificato un errore. La transazione è ora considerata interrotta e nessuna operazione è consentita al suo interno:
=> SELECT * FROM t;
ERRORE: la transazione corrente è stata annullata, i comandi vengono ignorati fino alla fine del blocco di transazione
E anche se si prova a confermare le modifiche, PostgreSQL segnalerà l'annullamento:
=> COMMIT;
ROLLBACK
Perché non si può continuare a eseguire la transazione dopo un errore? Il motivo è che l'errore potrebbe essersi verificato in modo tale da consentirci di accedere a parte delle modifiche — l'atomicità sarebbe stata violata non solo per la transazione, ma anche per l'istruzione. Come nel nostro esempio, in cui l'istruzione ha aggiornato una riga prima dell'errore:
=> SELEZIONA * DA heap_page('t', 0);
ctid | stato | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normale | 3669 (c) | 3672 | (0,4)
(0,2) | normale | 3670 (a) | 0 (a) | (0,2)
(0,3) | normale | 3671 (c) | 0 (a) | (0,3)
(0,4) | normale | 3672 | 0 (a) | (0,4)
(4 righe)
Va detto che in psql esiste una modalità che consente comunque di continuare l'elaborazione della transazione dopo un errore, come se le azioni dell'istruzione errata fossero annullate.
=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 righe)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERRORE: divisione per zero
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 righe)
=> COMMIT;
Non è difficile capire che, in questa modalità, psql imposta effettivamente un punto di salvataggio implicito prima di ogni comando e, in caso di errore, inizia il rollback a quel punto. Questa modalità non è utilizzata per impostazione predefinita, poiché impostare punti di salvataggio (anche senza rollback) comporta costi significativi.
Fonte: habr.com
