MVCC-3. Versions en ligne

Nous avons donc examiné les problèmes liés à l' isolement et fait une digression sur l' organisation des données à un faible niveau . Et enfin arrivé au plus intéressant - à la version des lignes.

Titre


Comme nous l'avons déjà dit, chaque ligne peut être simultanément présente dans la base de données en plusieurs versions. Une version doit être distinguée de l'autre d'une manière ou d'une autre: à cet effet, chaque version a deux marques qui déterminent le "temps" de l'action de cette version (xmin et xmax). Entre guillemets - car ce n'est pas le temps qui est utilisé comme tel, mais un compteur d'incrémentation spécial. Et ce compteur est le numéro de transaction.

(Comme d'habitude, c'est en fait plus compliqué: 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.)

Lorsque la ligne est créée, xmin est défini sur le numéro de la transaction qui a exécuté la commande INSERT et xmax n'est pas renseigné.

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

Lorsqu'une ligne est modifiée avec la commande UPDATE, deux opérations sont réellement effectuées: DELETE et INSERT. Dans la version actuelle de la ligne, xmax est défini comme égal au numéro de la transaction qui a effectué UPDATE. Ensuite, une nouvelle version de la même ligne est créée; sa valeur xmin correspond à la valeur xmax de la version précédente.

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

  • infomask - une série de bits définissant les propriétés de cette version. Il y en a beaucoup; les principaux que nous considérerons progressivement.
  • ctid - un lien vers la prochaine version plus récente de la même ligne. Dans la version la plus récente et la plus récente de la chaîne, 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 de série du pointeur dans le tableau.
  • bitmap de valeurs non définies - marque les colonnes de cette version qui contiennent une valeur non définie (NULL). NULL n'est pas l'une des valeurs habituelles des types de données, donc l'attribut doit être stocké séparément.

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

Insérer


Examinons de plus près comment les opérations de chaîne sont effectuées à un niveau bas et commençons par l'insertion.

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

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

Insérez une ligne, après avoir démarré la transaction.

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

Voici le numéro de notre transaction en cours:

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

Jetez un œil au contenu de la page. La fonction heap_page_items de l'extension pageinspect fournit des informations sur les pointeurs et les versions des 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 (tas) dans PostgreSQL fait référence aux tables. C'est une autre utilisation étrange du terme - le tas est une structure de données bien connue qui n'a rien à voir avec une table. Ici, ce mot est utilisé dans le sens de «tout est empilé en tas», contrairement aux index ordonnés.

La fonction affiche les données «telles quelles» dans un format difficile à lire. Pour comprendre, nous ne laisserons qu'une partie des informations et les décrypterons:

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

  • Nous avons ajouté un zéro au numéro d'index pour le mettre sous la même forme que t_ctid: (numéro de page, numéro d'index).
  • Déchiffré l'état du pointeur lp_flags. Ici, c'est «normal» - cela signifie que le pointeur fait vraiment référence à la version de la chaîne. D'autres valeurs seront prises en compte ultérieurement.
  • De tous les bits d'information, jusqu'à présent, seules deux paires ont été allouées. Les bits xmin_committed et xmin_aborted indiquent si la transaction avec le numéro xmin est validée (annulée). Deux bits similaires font référence au numéro de transaction xmax.

Que voyons-nous? Lorsque vous insérez une ligne dans la page du tableau, un pointeur apparaît avec le numéro 1, faisant référence à la première et seule version de la ligne.

Dans la version de la ligne, le champ xmin est rempli avec le numéro de la transaction en cours. La transaction est toujours active, donc les bits xmin_committed 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'une version plus récente n'existe pas.

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

Faisons un pas de plus pour améliorer la lisibilité en ajoutant des bits d'information aux numéros de transaction. Et nous allons créer une fonction, car nous aurons besoin de la demande 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; 

Dans ce formulaire, il est beaucoup plus clair ce qui se passe dans l'en-tête de la version de la chaîne:

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


Une fois la transaction réussie, vous devez vous souvenir de son état - notez qu'elle est corrigée. Pour ce faire, utilisez une structure appelée XACT (et avant la version 10, elle s'appelait CLOG (journal de validation) et ce nom peut toujours ê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. Dans ceux-ci, pour chaque transaction, deux bits sont alloués: validés et abandonnés - exactement les mêmes que dans l'en-tête de la version de la ligne. Ces informations sont divisées en plusieurs fichiers uniquement pour des raisons de commodité, nous reviendrons sur ce problème lorsque nous envisagerons le gel. Et le travail avec ces fichiers s'effectue page par page, comme avec tous les autres.

Ainsi, lors de la validation d'une transaction dans XACT, le bit validé est défini pour cette transaction. Et c'est tout ce qui se passe pendant le commit (bien que nous ne parlions pas encore du journal de pré-enregistrement).

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

  1. La transaction xmin est-elle terminée? Sinon, la version générée de la chaîne ne doit pas être visible.
    Cette vérification est effectuée en examinant encore une autre structure, qui se trouve dans la mémoire partagée de l'instance et s'appelle ProcArray. Il contient une liste de tous les processus actifs, et pour chacun le numéro de sa transaction actuelle (active) est indiqué.
  2. Une fois terminé, comment - par fixation ou annulation? En cas d'annulation, la version de la chaîne ne devrait pas non plus être visible.
    C'est exactement à cela que sert XACT. Mais, bien que les dernières pages XACT soient stockées dans des tampons en RAM, il n'est pas nécessaire de vérifier le XACT à chaque fois. Par conséquent, le statut d'une transaction une fois clarifiée est enregistré dans les bits xmin_committed et xmin_aborted de la version de ligne. Si l'un de ces bits est défini, l'état de la transaction xmin est considéré comme connu et la transaction suivante n'aura plus à 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 se terminera avec succès. Et au moment de la correction, il n'est déjà pas clair quelles lignes dans quelles pages ont été modifiées. Il peut y avoir de nombreuses pages de ce type, et les mémoriser est désavantageux. De plus, une partie des pages peut être poussée hors du cache tampon vers le disque; les relire pour changer les bits signifierait un ralentissement significatif de la validation.

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

Alors, corrigez le changement.

 => COMMIT; 

Rien n'a changé dans la page (mais nous savons que l'état 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) 

Maintenant, la transaction qui accède d'abord à la page 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) 

Effacer


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

Notez que la valeur xmax définie correspondant à la transaction active agit comme un verrou de ligne. Si une autre transaction est sur le point de mettre à jour ou de supprimer cette ligne, elle sera obligée d'attendre la fin de la transaction xmax. Nous parlerons plus en détail des verrous plus tard. Pour l'instant, nous notons seulement que le nombre de verrous de ligne est illimité. Ils n'occupent pas de place dans la RAM et les performances du système ne souffrent pas de leur quantité. Certes, les transactions «longues» présentent d'autres inconvénients, mais nous en reparlerons plus tard.

Supprimez la ligne.

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

Nous voyons que le numéro de transaction est enregistré 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) 

Annuler


L'inversion des modifications fonctionne de la même manière que la validation, uniquement dans XACT pour la transaction le bit abandonné est défini. L'annulation est aussi rapide que la validation. Bien que la commande soit appelée ROLLBACK, la modification n'est pas annulée: 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 de conseil xmax_aborted sera défini dans la version de la ligne. Le nombre xmax lui-même reste dans 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 demande produit une ligne (nouvelle version):

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

Mais dans 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 distante est marquée du numéro de transaction actuel dans le champ xmax. De plus, cette valeur est écrite sur l'ancienne, puisque la transaction précédente a été annulée. Et le bit xmax_aborted est réinitialisé, car le statut de la transaction en cours est encore inconnu.

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

Un deuxième pointeur et une deuxième ligne apparaissent dans la page d'index, reliant à la deuxième version dans la page de table.

Comme pour la suppression, la valeur xmax dans la première version de la chaîne est un signe que la chaîne est verrouillée.

Eh bien, terminez la transaction.

 => COMMIT; 

Indices


Jusqu'à présent, nous n'avons parlé que des pages tabulaires. Et que se passe-t-il à l'intérieur des indices?

Les informations contenues dans les pages d'index dépendent fortement du type particulier d'index. Et même un type d'index a différents types de pages. Par exemple, l'arbre B a une page avec des métadonnées et des pages «normales».

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

Les lignes dans les index peuvent également avoir une structure très différente selon le type d'index. Par exemple, pour un arbre B, les lignes liées aux pages feuilles contiennent la valeur de la clé d'index et un lien (ctid) vers la ligne correspondante du tableau. En général, un index peut être organisé de manière complètement différente.

Le point le plus important est qu'il n'y a aucune version de ligne dans aucun type d'index. 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 tabulaires des lignes - vous ne pouvez donc déterminer quelle version la transaction verra si vous regardez le tableau. (Comme d'habitude, ce n'est pas toute la vérité. Dans certains cas, la carte de visibilité vous permet d'optimiser le processus, mais nous l'examinerons plus en détail plus tard.)

Dans le même temps, dans la page d'index, nous trouvons des pointeurs vers les deux versions, à la fois actuelle et 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 pour «enregistrer» les numéros de transaction.

Si une transaction ne lit que des données, elle n'affecte pas la visibilité des versions de ligne. Par conséquent, au début, le processus de desserte émet une transaction de numéro virtuel (xid virtuel). Le numéro se compose d'un identifiant de processus et d'un numéro séquentiel.

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

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

À différents moments, des transactions virtuelles avec des numéros déjà utilisés peuvent bien apparaître dans le système, ce qui est normal. Mais un tel nombre ne peut pas être écrit sur les pages de données, car la prochaine fois que vous accédez à la page, il peut perdre tout son sens.

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

Si la transaction commence à modifier des 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


Enregistrer des points


SQL définit des points de sauvegarde qui vous permettent d'annuler une partie d'une transaction sans l'interrompre complètement. Mais cela ne rentre pas dans le schéma ci-dessus, car le statut d'une transaction est celui de toutes ses modifications, et physiquement aucune donnée n'est annulée.

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

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

Les informations sur l'imbrication des transactions sont stockées dans des fichiers dans le 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 et les transactions autonomes. Les transactions autonomes ne dépendent en aucune manière les unes des autres, et les transactions imbriquées sont dépendantes. Il n'y a pas de transactions autonomes dans PostgreSQL habituel, et peut-être pour le mieux: dans le cas où elles sont nécessaires très, très rarement, et leur présence dans d'autres SGBD provoque des abus, dont tout le monde souffre alors.

Effacez la table, démarrez la transaction et insérez 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) 

Placez maintenant un point de sauvegarde et insérez 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 la transaction principale, non imbriquée.

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

Nous 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 de 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 maintenant 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 utilisé 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 pendant l'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 s'est produite. Maintenant, la transaction est considérée comme abandonnée et aucune opération n'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 l'annulation:

 => COMMIT; 
 ROLLBACK 

Pourquoi ne puis-je pas continuer la transaction après un échec? Le fait est qu'une erreur pourrait se produire afin que nous puissions accéder à une partie des changements - l'atomicité même pas de la transaction, mais l'opérateur serait violé. 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) 

Je dois dire que dans psql, il existe un mode qui vous permet toujours de continuer la transaction 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 est facile de deviner que dans ce mode, psql définit en fait un point de sauvegarde implicite devant chaque commande, et en cas d'échec, il lance une restauration. Ce mode n'est pas utilisé par défaut, car la définition de points de sauvegarde (même sans y revenir) est associée à une surcharge importante.

À suivre.

Source: https://habr.com/ru/post/fr445820/


All Articles