MVCC-3. Versions de chaînes

Nous avons donc examiné les questions liées à isolation, et fit une retraite vers organiser les données à un niveau bas. Et enfin, nous sommes arrivés à la partie la plus intéressante : les versions avec chaînes.

Titre

Comme nous l'avons déjà dit, chaque ligne peut exister simultanément dans plusieurs versions de la base de données. Une version doit être d'une manière ou d'une autre distinguée d'une autre. Pour cela, chaque version comporte deux marques qui déterminent le « temps » d'action de cette version (xmin et xmax). Entre guillemets - car ce n'est pas le temps en tant que tel qui est utilisé, mais un compteur croissant spécial. Et ce compteur est le numéro de transaction.

(Comme d'habitude, la réalité est plus compliquée : le numéro de transaction ne peut pas augmenter tout le temps en raison de la capacité en bits limitée du compteur. Mais nous examinerons ces détails en détail lorsque nous arriverons au gel.)

Lorsqu'une ligne est créée, xmin est défini sur le numéro de transaction qui a émis la commande INSERT et xmax reste vide.

Lorsqu'une ligne est supprimée, la valeur xmax de la version actuelle est marquée du numéro de la transaction qui a effectué la DELETE.

Lorsqu'une ligne est modifiée par une commande UPDATE, deux opérations sont en réalité effectuées : DELETE et INSERT. La version actuelle de la ligne définit xmax égal au numéro de la transaction qui a effectué la MISE À JOUR. Une nouvelle version de la même chaîne est alors créée ; sa valeur xmin coïncide avec la valeur xmax de la version précédente.

Les champs xmin et xmax sont inclus dans l'en-tête de version de la ligne. En plus de ces champs, l'en-tête en contient d'autres, par exemple :

  • infomask est une série de bits qui définissent les propriétés de cette version. Il y en a beaucoup ; Nous examinerons progressivement les principaux.
  • ctid est un lien vers la version suivante, plus récente, de la même ligne. Pour la version la plus récente et la plus actuelle d'une ligne, le ctid fait référence à cette version elle-même. Le numéro a la forme (x,y), où x est le numéro de page, y est le numéro d'index dans le tableau.
  • bitmap null - Marque les colonnes d'une version donnée qui contiennent une valeur nulle (NULL). NULL ne fait pas partie des valeurs de type de données normales, l'attribut doit donc être stocké séparément.

En conséquence, l'en-tête est assez volumineux - au moins 23 octets pour chaque version de la ligne, et généralement plus en raison du bitmap NULL. Si le tableau est « étroit » (c’est-à-dire qu’il contient peu de colonnes), la surcharge peut prendre plus que les informations utiles.

insérer

Examinons de plus près comment les opérations sur les chaînes de bas niveau sont effectuées, en commençant par l'insertion.

Pour des expériences, créons une nouvelle table avec deux colonnes et un index sur l'une d'elles :

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

Insérons une ligne après avoir démarré une transaction.

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

Voici notre numéro de transaction actuel :

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

Regardons le contenu de la page. La fonction heap_page_items de l'extension pageinspect permet d'obtenir des informations sur les pointeurs et les versions de lignes :

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

Notez que le mot tas dans PostgreSQL fait référence aux tables. C'est une autre utilisation étrange du terme - un tas est connu Structure de données, qui n'a rien de commun avec la table. Ici, le mot est utilisé dans le sens de « tout est réuni », par opposition aux index ordonnés.

La fonction affiche les données « telles quelles », dans un format difficile à comprendre. Pour le comprendre, nous ne laisserons qu'une partie de l'information et la déchiffrerons :

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

Voici ce que nous avons fait :

  • Ajout d'un zéro au numéro d'index pour qu'il ressemble à t_ctid : (numéro de page, numéro d'index).
  • Déchiffrement de l'état du pointeur lp_flags. Ici, c'est "normal" - cela signifie que le pointeur fait réellement référence à la version de la chaîne. Nous examinerons d'autres significations plus tard.
  • Parmi tous les bits d’information, seules deux paires ont été identifiées jusqu’à présent. Les bits xmin_commit et xmin_aborted indiquent si la transaction numéro xmin est validée (abandonnée). Deux bits similaires font référence au numéro de transaction xmax.

Que voit-on ? Lorsque vous insérez une ligne, un numéro d'index 1 apparaîtra dans la page du tableau, pointant vers la première et unique version de la ligne.

Dans la version chaîne, le champ xmin est renseigné avec le numéro de transaction en cours. La transaction est toujours active, donc les bits xmin_commit et xmin_aborted ne sont pas définis.

Le champ ctid de la version de ligne fait référence à la même ligne. Cela signifie qu'il n'existe pas de version plus récente.

Le champ xmax est rempli avec un numéro factice 0 car cette version de la ligne n'a pas été supprimée et est actuelle. Les transactions ne prêteront pas attention à ce nombre car le bit xmax_aborted est défini.

Faisons un pas de plus vers l'amélioration de la lisibilité en ajoutant des bits d'information aux numéros de transaction. Et créons une fonction, puisque nous aurons besoin de la requête plus d'une fois :

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

Sous cette forme, ce qui se passe dans l'en-tête de la version ligne est beaucoup plus clair :

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

Des informations similaires, mais nettement moins détaillées, peuvent être obtenues à partir du tableau lui-même, en utilisant les pseudo-colonnes xmin et xmax :

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

Fixation

Si une transaction est terminée avec succès, vous devez vous souvenir de son statut – notez qu'elle est validée. Pour ce faire, une structure appelée XACT est utilisée (et avant la version 10 elle s'appelait CLOG (commit log) et ce nom peut encore être trouvé à différents endroits).

XACT n'est pas une table de catalogue système ; ce sont les fichiers du répertoire PGDATA/pg_xact. Ils ont deux bits pour chaque transaction : validée et abandonnée - tout comme dans l'en-tête de version de ligne. Ces informations sont réparties en plusieurs fichiers uniquement par commodité ; nous reviendrons sur cette question lorsque nous envisagerons le gel. Et le travail avec ces fichiers s'effectue page par page, comme avec tous les autres.

Ainsi, lorsqu'une transaction est validée dans XACT, le bit validé est défini pour cette transaction. Et c'est tout ce qui se passe lors de la validation (même si nous ne parlons pas encore du journal de pré-enregistrement).

Lorsqu'une autre transaction accède à la page de tableau que nous venons de consulter, elle devra répondre à plusieurs questions.

  1. La transaction xmin est-elle terminée ? Sinon, la version créée de la chaîne ne doit pas être visible.
    Cette vérification est effectuée en examinant une autre structure, située dans la mémoire partagée de l'instance et appelée ProcArray. Il contient une liste de tous les processus actifs, et pour chacun d'entre eux le numéro de sa transaction actuelle (active) est indiqué.
  2. Si c'est terminé, comment - en s'engageant ou en annulant ? En cas d'annulation, la version de la ligne ne devrait pas non plus être visible.
    C'est exactement à cela que sert XACT. Mais, bien que les dernières pages de XACT soient stockées dans des tampons en RAM, il reste coûteux de vérifier XACT à chaque fois. Par conséquent, une fois le statut de la transaction déterminé, il est écrit dans les bits xmin_commit et xmin_aborted de la version de chaîne. Si l'un de ces bits est défini, alors l'état de la transaction xmin est considéré comme connu et la transaction suivante n'aura pas besoin d'accéder à XACT.

Pourquoi ces bits ne sont-ils pas définis par la transaction elle-même qui effectue l'insertion ? Lorsqu'une insertion se produit, la transaction ne sait pas encore si elle réussira. Et au moment de la validation, on ne sait plus quelles lignes dans quelles pages ont été modifiées. Il peut y avoir beaucoup de ces pages et les mémoriser n'est pas rentable. De plus, certaines pages peuvent être expulsées du cache tampon vers le disque ; les relire pour changer les bits ralentirait considérablement la validation.

L'inconvénient des économies est qu'après des modifications, toute transaction (même celle effectuant une simple lecture - SELECT) peut commencer à modifier les pages de données dans le cache tampon.

Alors, corrigeons le changement.

=> COMMIT;

Rien n'a changé sur la page (mais on sait que le statut de la transaction est déjà enregistré dans XACT) :

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

Désormais, la transaction qui accède à la page en premier devra déterminer l'état de la transaction xmin et l'écrire dans les bits d'information :

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

Enlèvement

Lorsqu'une ligne est supprimée, le numéro de la transaction de suppression en cours est écrit dans le champ xmax de la version actuelle et le bit xmax_aborted est effacé.

Notez que la valeur définie de xmax correspondant à la transaction active agit comme un verrou de ligne. Si une autre transaction souhaite mettre à jour ou supprimer cette ligne, elle sera obligée d'attendre la fin de la transaction xmax. Nous parlerons davantage du blocage plus tard. Pour l’instant, notons simplement que le nombre de row locks est illimité. Ils ne prennent pas de place dans la RAM et les performances du système ne souffrent pas de leur nombre. Certes, les transactions « longues » présentent d'autres inconvénients, mais nous y reviendrons plus tard.

Supprimons la ligne.

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

On voit que le numéro de transaction est écrit dans le champ xmax, mais les bits d'information ne sont pas définis :

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

Annulation

L'abandon des modifications fonctionne de la même manière que la validation, uniquement dans XACT, le bit abandonné est défini pour la transaction. Annuler est aussi rapide que commettre. Bien que la commande s'appelle ROLLBACK, les modifications ne sont pas annulées : tout ce que la transaction a réussi à modifier dans les pages de données reste inchangé.

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

Lors de l'accès à la page, l'état sera vérifié et le bit d'indication xmax_aborted sera défini sur la version de la ligne. Le numéro xmax lui-même reste sur la page, mais personne ne le regardera.

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

Mettre à jour

La mise à jour fonctionne comme si elle supprimait d'abord la version actuelle de la ligne, puis en insérait une nouvelle.

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

La requête produit une ligne (nouvelle version) :

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

Mais sur la page, nous voyons les deux 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 version supprimée est marquée du numéro de transaction en cours dans le champ xmax. De plus, cette valeur est écrasée par rapport à l'ancienne, puisque la transaction précédente a été annulée. Et le bit xmax_aborted est effacé car l'état de la transaction en cours n'est pas encore connu.

La première version de la ligne fait désormais référence à la seconde (champ t_ctid) comme étant la plus récente.

Un deuxième index apparaît dans la page d'index et une deuxième ligne fait référence à la deuxième version dans la page de table.

Tout comme pour la suppression, la valeur xmax dans la première version de la ligne indique que la ligne est verrouillée.

Eh bien, terminons la transaction.

=> COMMIT;

Indices

Jusqu'à présent, nous n'avons parlé que des pages de tableau. Que se passe-t-il à l’intérieur des index ?

Les informations contenues dans les pages d'index varient considérablement en fonction du type spécifique d'index. Et même un type d’index comporte différents types de pages. Par exemple, un B-tree possède une page de métadonnées et des pages « normales ».

Cependant, la page comporte généralement un tableau de pointeurs vers les lignes et les lignes elles-mêmes (tout comme une page de tableau). De plus, à la fin de la page, il y a un espace pour des données spéciales.

Les lignes des index peuvent également avoir des structures très différentes selon le type d'index. Par exemple, pour un B-tree, les lignes liées aux pages feuilles contiennent la valeur de la clé d'indexation et une référence (ctid) à la ligne du tableau correspondante. En général, l'index peut être structuré d'une manière complètement différente.

Le point le plus important est qu’il n’y a aucune version de ligne dans les index de quelque type que ce soit. Eh bien, ou nous pouvons supposer que chaque ligne est représentée par exactement une version. En d’autres termes, il n’y a pas de champs xmin et xmax dans l’en-tête de la ligne d’index. Nous pouvons supposer que les liens de l'index mènent à toutes les versions de table des lignes - vous pouvez donc déterminer quelle version la transaction verra uniquement en regardant la table. (Comme toujours, ce n’est pas toute la vérité. Dans certains cas, la carte de visibilité peut optimiser le processus, mais nous y reviendrons plus en détail plus tard.)

En même temps, dans la page d'index, nous trouvons des pointeurs vers les deux versions, à la fois l'actuelle et l'ancienne :

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

Transactions virtuelles

En pratique, PostgreSQL utilise des optimisations qui lui permettent de « sauvegarder » les numéros de transactions.

Si une transaction lit uniquement des données, cela n'a aucun effet sur la visibilité des versions de lignes. Par conséquent, le processus de service émet d’abord un xid virtuel pour la transaction. Le numéro se compose d'un ID de processus et d'un numéro de séquence.

L'émission de ce numéro ne nécessite pas de synchronisation entre tous les processus et est donc très rapide. Nous connaîtrons une autre raison d'utiliser des numéros virtuels lorsque nous parlerons de gel.

Les numéros virtuels ne sont en aucun cas pris en compte dans les instantanés de données.

À différents moments, il peut y avoir des transactions virtuelles dans le système avec des numéros déjà utilisés, ce qui est normal. Mais un tel nombre ne peut pas être écrit dans des pages de données, car lors du prochain accès à la page, il risque de perdre toute signification.

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

Si une transaction commence à modifier les données, un numéro de transaction réel et unique lui est attribué.

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

=> COMMIT;

Transactions imbriquées

Économisez des points

Défini en SQL économiser des points (savepoint), qui permettent d'annuler une partie d'une transaction sans l'interrompre complètement. Mais cela ne rentre pas dans le diagramme ci-dessus, car la transaction a le même statut pour toutes ses modifications et physiquement aucune donnée n'est annulée.

Pour implémenter cette fonctionnalité, une transaction avec un point de sauvegarde est divisée en plusieurs transactions imbriquées (sous-transaction), dont le statut peut être géré séparément.

Les transactions imbriquées ont leur propre numéro (supérieur au numéro de la transaction principale). Le statut des transactions imbriquées est enregistré de la manière habituelle dans XACT, mais le statut final dépend du statut de la transaction principale : si elle est annulée, alors toutes les transactions imbriquées sont également annulées.

Les informations sur l'imbrication des transactions sont stockées dans des fichiers du répertoire PGDATA/pg_subtrans. Les fichiers sont accessibles via des tampons dans la mémoire partagée de l'instance, organisés de la même manière que les tampons XACT.

Ne confondez pas les transactions imbriquées avec les transactions autonomes. Les transactions autonomes ne dépendent en aucune manière les unes des autres, contrairement aux transactions imbriquées. Il n'y a pas de transactions autonomes dans PostgreSQL classique, et peut-être pour le mieux : elles sont très, très rarement nécessaires, et leur présence dans d'autres SGBD provoque des abus, dont tout le monde souffre alors.

Vidons le tableau, démarrons une transaction et insérons la ligne :

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

Maintenant, mettons un point de sauvegarde et insérons une autre ligne.

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

Notez que la fonction txid_current() renvoie le numéro de transaction principale, pas le numéro de transaction imbriqué.

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

Revenons au point de sauvegarde et insérons la troisième ligne.

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

Dans la page, nous continuons à voir la ligne ajoutée par la transaction imbriquée annulée.

Nous corrigeons les changements.

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

Vous pouvez désormais voir clairement que chaque transaction imbriquée a son propre statut.

Notez que les transactions imbriquées ne peuvent pas être utilisées explicitement dans SQL, c'est-à-dire que vous ne pouvez pas démarrer une nouvelle transaction sans terminer la transaction en cours. Ce mécanisme est activé implicitement lors de l'utilisation de points de sauvegarde, ainsi que lors de la gestion des exceptions PL/pgSQL et dans un certain nombre d'autres cas plus exotiques.

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

Erreurs et atomicité des opérations

Que se passe-t-il si une erreur se produit lors de l'exécution d'une opération ? Par exemple, comme ceci :

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

Une erreur est survenue. Désormais, la transaction est considérée comme abandonnée et aucune opération n'y est autorisée :

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

Et même si vous essayez de valider les modifications, PostgreSQL signalera un abandon :

=> COMMIT;
ROLLBACK

Pourquoi une transaction ne peut-elle pas continuer après un échec ? Le fait est qu'une erreur pourrait survenir de telle manière que nous aurions accès à une partie des modifications - l'atomicité non même de la transaction, mais de l'opérateur serait violée. Comme dans notre exemple, où l'opérateur a réussi à mettre à jour une ligne avant l'erreur :

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

Il faut dire que psql dispose d'un mode qui permet toujours à la transaction de continuer après un échec comme si les actions de l'opérateur erroné étaient annulées.

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

Il n'est pas difficile de deviner que dans ce mode, psql place en fait un point de sauvegarde implicite avant chaque commande et, en cas d'échec, initie une restauration. Ce mode n'est pas utilisé par défaut, car la définition de points de sauvegarde (même sans y revenir) implique une surcharge importante.

En savoir plus.

Source: habr.com

Ajouter un commentaire