MVCC-3. Versions de cadena

Per tant, hem considerat qüestions relacionades amb aïllament, i va fer una retirada al voltant organitzar les dades a un nivell baix. I finalment hem arribat a la part més interessant: les versions de corda.

Títol

Com ja hem dit, cada fila pot existir simultàniament en diverses versions a la base de dades. Una versió s'ha de distingir d'alguna manera d'una altra. Per això, cada versió té dues marques que determinen el "temps" d'acció d'aquesta versió (xmin i xmax). Entre cometes, perquè no s'utilitza el temps com a tal, sinó un comptador creixent especial. I aquest comptador és el número de transacció.

(Com és habitual, la realitat és més complicada: el número de transacció no pot augmentar tot el temps a causa de la capacitat limitada de bits del comptador. Però veurem aquests detalls amb detall quan arribem a la congelació).

Quan es crea una fila, xmin s'estableix en el número de transacció que ha emès l'ordre INSERT i xmax es deixa en blanc.

Quan s'elimina una fila, el valor xmax de la versió actual es marca amb el número de la transacció que ha realitzat l'ELIMINAR.

Quan una fila es modifica mitjançant una ordre UPDATE, en realitat es realitzen dues operacions: DELETE i INSERT. La versió actual de la fila estableix xmax igual al número de la transacció que ha realitzat l'ACTUALITZACIÓ. Aleshores es crea una nova versió de la mateixa cadena; el seu valor xmin coincideix amb el valor xmax de la versió anterior.

Els camps xmin i xmax s'inclouen a la capçalera de la versió de fila. A més d'aquests camps, la capçalera conté altres, per exemple:

  • infomask és una sèrie de bits que defineixen les propietats d'aquesta versió. N'hi ha força; A poc a poc anirem considerant els principals.
  • ctid és un enllaç a la següent versió més nova de la mateixa línia. Per a la versió més recent i actual d'una línia, el ctid fa referència a aquesta versió en si. El nombre té la forma (x,y), on x és el número de pàgina, y és el número d'índex de la matriu.
  • mapa de bits nul - Marca aquelles columnes d'una versió determinada que contenen un valor nul (NULL). NULL no és un dels valors de tipus de dades normals, de manera que l'atribut s'ha d'emmagatzemar per separat.

Com a resultat, la capçalera és bastant gran: almenys 23 bytes per a cada versió de la línia, i normalment més a causa del mapa de bits NULL. Si la taula és "estreta" (és a dir, conté poques columnes), la sobrecàrrega pot ocupar més que la informació útil.

inserir

Fem una ullada més de prop a com es realitzen les operacions de cadena de baix nivell, començant per la inserció.

Per als experiments, creem una taula nova amb dues columnes i un índex en una d'elles:

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

Inseriu una fila després d'iniciar una transacció.

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

Aquest és el nostre número de transacció actual:

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

Vegem el contingut de la pàgina. La funció heap_page_items de l'extensió pageinspect us permet obtenir informació sobre punters i versions de files:

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

Tingueu en compte que la paraula munt a PostgreSQL fa referència a taules. Aquest és un altre ús estrany del terme: es coneix un munt estructura de dades, que no té res en comú amb la taula. Aquí la paraula s'utilitza en el sentit de "tot està junt", a diferència dels índexs ordenats.

La funció mostra les dades "tal com estan", en un format difícil d'entendre. Per esbrinar-ho, deixarem només una part de la informació i la desxifrarem:

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

Això és el que vam fer:

  • S'ha afegit un zero al número d'índex perquè tingui el mateix aspecte que t_ctid: (número de pàgina, número d'índex).
  • S'ha desxifrat l'estat del punter lp_flags. Aquí és "normal", això vol dir que el punter es refereix realment a la versió de la cadena. Veurem altres significats més endavant.
  • De tots els bits d'informació, fins ara només s'han identificat dos parells. Els bits xmin_committed i xmin_aborted indiquen si el número de transacció xmin està compromès (avortat). Dos bits similars fan referència al número de transacció xmax.

Què veiem? Quan inseriu una fila, apareixerà un número d'índex 1 a la pàgina de la taula, que assenyala la primera i única versió de la fila.

A la versió de cadena, el camp xmin s'omple amb el número de transacció actual. La transacció encara està activa, de manera que els bits xmin_committed i xmin_aborted no estan establerts.

El camp ctid de la versió de fila fa referència a la mateixa fila. Això vol dir que no existeix una versió més nova.

El camp xmax s'omple amb un número fictici 0 perquè aquesta versió de la fila no s'ha suprimit i és actual. Les transaccions no prestaran atenció a aquest número perquè el bit xmax_aborted està establert.

Fem un pas més per millorar la llegibilitat afegint bits d'informació als números de transacció. I creem una funció, ja que necessitarem la sol·licitud més d'una vegada:

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

En aquest formulari, queda molt més clar què passa a la capçalera de la versió de fila:

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

Es pot obtenir informació similar, però significativament menys detallada, de la mateixa taula, utilitzant les pseudocolumnes xmin i xmax:

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

Fixació

Si una transacció es completa amb èxit, cal que recordeu el seu estat; tingueu en compte que està compromesa. Per fer-ho, s'utilitza una estructura anomenada XACT (i abans de la versió 10 es deia CLOG (commit log) i aquest nom encara es pot trobar en diferents llocs).

XACT no és una taula de catàleg del sistema; aquests són els fitxers del directori PGDATA/pg_xact. Tenen dos bits per a cada transacció: compromès i avortat, igual que a la capçalera de la versió de fila. Aquesta informació es divideix en diversos fitxers únicament per comoditat; tornarem a aquest problema quan considerem la congelació. I el treball amb aquests fitxers es realitza pàgina per pàgina, com amb tots els altres.

Així, quan es confirma una transacció a XACT, el bit compromès s'estableix per a aquesta transacció. I això és tot el que passa durant la confirmació (tot i que encara no estem parlant del registre de pre-enregistrament).

Quan una altra transacció accedeixi a la pàgina de la taula que acabem de mirar, haurà de respondre diverses preguntes.

  1. S'ha completat la transacció xmin? Si no, la versió creada de la cadena no hauria de ser visible.
    Aquesta comprovació es realitza mirant una altra estructura, que es troba a la memòria compartida de la instància i s'anomena ProcArray. Conté una llista de tots els processos actius, i per a cadascun s'indica el número de la seva transacció (activa) actual.
  2. Si s'ha completat, aleshores com - comprometent-se o cancel·lant? Si es cancel·la, la versió de fila tampoc hauria de ser visible.
    Això és exactament per al que serveix XACT. Però, tot i que les últimes pàgines de XACT s'emmagatzemen en memòria intermèdia de la memòria RAM, encara és car comprovar XACT cada vegada. Per tant, un cop determinat l'estat de la transacció, s'escriu als bits xmin_committed i xmin_aborted de la versió de cadena. Si s'estableix un d'aquests bits, l'estat de la transacció xmin es considera conegut i la següent transacció no haurà d'accedir a XACT.

Per què aquests bits no estan establerts per la transacció mateixa fent la inserció? Quan es produeix una inserció, la transacció encara no sap si tindrà èxit. I en el moment de comprometre's, ja no està clar en quines línies es van canviar les pàgines. Pot ser que hi hagi moltes pàgines d'aquest tipus i memoritzar-les no és rendible. A més, algunes pàgines es poden desallotjar de la memòria cau de memòria intermèdia al disc; llegir-los de nou per canviar els bits alentiria significativament la confirmació.

L'inconvenient de l'estalvi és que, després dels canvis, qualsevol transacció (fins i tot una que faci una lectura senzilla - SELECT) pot començar a canviar les pàgines de dades a la memòria cau del buffer.

Per tant, registrem el canvi.

=> COMMIT;

No ha canviat res a la pàgina (però sabem que l'estat de la transacció ja està registrat a XACT):

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

Ara, la transacció que accedeix primer a la pàgina haurà de determinar l'estat de la transacció xmin i escriure'l als bits d'informació:

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

Esborrat

Quan s'elimina una fila, el número de la transacció d'eliminació actual s'escriu al camp xmax de la versió actual i s'esborra el bit xmax_aborted.

Tingueu en compte que el valor establert de xmax corresponent a la transacció activa actua com un bloqueig de fila. Si una altra transacció vol actualitzar o suprimir aquesta fila, es veurà obligat a esperar que la transacció xmax es completi. Més endavant parlarem del bloqueig. De moment, només observem que el nombre de bloquejos de fila és il·limitat. No ocupen espai a la memòria RAM i el rendiment del sistema no pateix el seu nombre. És cert que les transaccions "llargues" tenen altres desavantatges, però en parlarem més endavant.

Suprimem la línia.

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

Veiem que el número de transacció està escrit al camp xmax, però els bits d'informació no estan establerts:

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

Cancel · lació

L'avortament dels canvis funciona de manera semblant a la confirmació, només a XACT el bit avortat s'estableix per a la transacció. Desfer és tan ràpid com comprometre's. Tot i que l'ordre s'anomena ROLLBACK, els canvis no es reverteixen: tot el que la transacció ha aconseguit canviar a les pàgines de dades es manté sense canvis.

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

Quan s'accedeixi a la pàgina, es comprovarà l'estat i el bit d'indicació xmax_aborted s'establirà a la versió de fila. El nombre xmax en si es manté a la pàgina, però ningú el mirarà.

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

Actualitzar

L'actualització funciona com si primer hagués suprimit la versió actual de la fila i després n'hagués inserit una de nova.

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

La consulta produeix una línia (nova versió):

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

Però a la pàgina veiem les dues versions:

=> 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 versió suprimida està marcada amb el número de transacció actual al camp xmax. A més, aquest valor s'escriu sobre l'antic, ja que la transacció anterior es va cancel·lar. I el bit xmax_aborted s'esborra perquè encara no es coneix l'estat de la transacció actual.

La primera versió de la línia ara fa referència a la segona (camp t_ctid) com la més nova.

Un segon índex apareix a la pàgina d'índex i una segona fila fa referència a la segona versió a la pàgina de la taula.

Igual que amb la supressió, el valor xmax de la primera versió de la fila és una indicació que la fila està bloquejada.

Bé, completem la transacció.

=> COMMIT;

Índexs

Fins ara només hem parlat de pàgines de taula. Què passa dins dels índexs?

La informació de les pàgines d'índex varia molt segons el tipus específic d'índex. I fins i tot un tipus d'índex té diferents tipus de pàgines. Per exemple, un arbre B té una pàgina de metadades i pàgines "normals".

Tanmateix, la pàgina sol tenir una matriu de punters a les files i a les files mateixes (igual que una pàgina de taula). A més, al final de la pàgina hi ha espai per a dades especials.

Les files dels índexs també poden tenir estructures molt diferents segons el tipus d'índex. Per exemple, per a un arbre B, les files relacionades amb les pàgines de fulla contenen el valor de la clau d'indexació i una referència (ctid) a la fila de taula corresponent. En general, l'índex es pot estructurar d'una manera completament diferent.

El punt més important és que no hi ha versions de fila als índexs de cap tipus. Bé, o podem suposar que cada línia està representada exactament per una versió. En altres paraules, no hi ha camps xmin i xmax a la capçalera de la fila de l'índex. Podem suposar que els enllaços de l'índex condueixen a totes les versions de la taula de les files, de manera que només podeu esbrinar quina versió veurà la transacció mirant la taula. (Com sempre, aquesta no és tota la veritat. En alguns casos, el mapa de visibilitat pot optimitzar el procés, però ho veurem amb més detall més endavant).

Al mateix temps, a la pàgina d'índex trobem punters a les dues versions, tant l'actual com l'antiga:

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

Transaccions virtuals

A la pràctica, PostgreSQL utilitza optimitzacions que li permeten "desar" els números de transacció.

Si una transacció només llegeix dades, no té cap efecte sobre la visibilitat de les versions de fila. Per tant, el procés de servei emet primer un xid virtual a la transacció. El número consta d'un ID de procés i un número de seqüència.

L'emissió d'aquest número no requereix sincronització entre tots els processos i, per tant, és molt ràpid. Ens familiaritzarem amb un altre motiu per utilitzar números virtuals quan parlem de congelació.

Els números virtuals no es tenen en compte de cap manera en les instantànies de dades.

En diferents moments, pot haver-hi transaccions virtuals al sistema amb números que ja s'han utilitzat, i això és normal. Però aquest nombre no es pot escriure a les pàgines de dades, perquè la propera vegada que s'accedeixi a la pàgina pot perdre tot el significat.

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

Si una transacció comença a canviar les dades, se li dóna un número de transacció real i únic.

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

=> COMMIT;

Transaccions imbricades

Guardar punts

Definit en SQL estalviar punts (punt de guarda), que permeten cancel·lar part d'una transacció sense interrompre-la completament. Però això no encaixa en el diagrama anterior, ja que la transacció té el mateix estat per a tots els seus canvis, i físicament no es desfereix cap dada.

Per implementar aquesta funcionalitat, una transacció amb un punt de desat es divideix en diversos separats transaccions imbricades (subtransacció), l'estat de la qual es pot gestionar per separat.

Les transaccions imbricades tenen el seu propi número (superior al número de la transacció principal). L'estat de les transaccions imbricades es registra de la manera habitual a XACT, però l'estat final depèn de l'estat de la transacció principal: si es cancel·la, també es cancel·len totes les transaccions imbricades.

La informació sobre l'imbricació de transaccions s'emmagatzema als fitxers del directori PGDATA/pg_subtrans. S'accedeix als fitxers mitjançant les memòries intermèdies a la memòria compartida de la instància, organitzades de la mateixa manera que les memòries intermèdies XACT.

No confongueu transaccions imbricades amb transaccions autònomes. Les transaccions autònomes no depenen les unes de les altres de cap manera, però sí les transaccions imbricades. No hi ha transaccions autònomes a PostgreSQL habitual, i, potser, millor: es necessiten molt i molt poques vegades, i la seva presència en altres SGBD provoca abús, que després pateixen tothom.

Netegem la taula, comencem una transacció i inserim la fila:

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

Ara posem un punt de desat i inserim una altra línia.

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

Tingueu en compte que la funció txid_current() retorna el número de transacció principal, no el número de transacció imbricat.

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

Tornem al punt de desat i inserim la tercera línia.

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

A la pàgina continuem veient la fila afegida per la transacció imbricada cancel·lada.

Arreglem els canvis.

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

Ara podeu veure clarament que cada transacció imbricada té el seu propi estat.

Tingueu en compte que les transaccions imbricades no es poden utilitzar explícitament a SQL, és a dir, no podeu iniciar una transacció nova sense completar l'actual. Aquest mecanisme s'activa implícitament quan s'utilitzen punts de salvament, així com quan es gestionen excepcions PL/pgSQL i en una sèrie d'altres casos més exòtics.

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

Errors i atomicitat de les operacions

Què passa si es produeix un error mentre es realitza una operació? Per exemple, així:

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

S'ha produït un error. Ara la transacció es considera avortada i no s'hi permet cap operació:

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

I fins i tot si intenteu confirmar els canvis, PostgreSQL informarà d'un avortament:

=> COMMIT;
ROLLBACK

Per què una transacció no pot continuar després d'un error? El fet és que podria sorgir un error de tal manera que tindríem accés a part dels canvis: ni tan sols l'atomicitat de la transacció, sinó l'operador seria violat. Com en el nostre exemple, on l'operador va aconseguir actualitzar una línia abans de l'error:

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

Cal dir que psql té un mode que encara permet que la transacció continuï després d'una fallada com si les accions de l'operador errònia s'haguessin recuperat.

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

No és difícil endevinar que en aquest mode, psql en realitat posa un punt de desat implícit abans de cada ordre i, en cas d'error, inicia una recuperació. Aquest mode no s'utilitza de manera predeterminada, ja que establir punts de desat (fins i tot sense tornar-hi) implica una sobrecàrrega important.

Continuació.

Font: www.habr.com

Afegeix comentari