MVCC dans PostgreSQL-3. Versions en ligne

Eh bien, nous avons déjà discuté de l' isolement et fait une digression concernant la structure des données de bas niveau . Et nous avons enfin atteint la chose la plus fascinante, à savoir les versions en ligne (tuples).

En-tête de tuple


Comme déjà mentionné, plusieurs versions de chaque ligne peuvent être disponibles simultanément dans la base de données. Et nous devons en quelque sorte distinguer une version d'une autre. À cette fin, chaque version est étiquetée avec son «temps» xmin ( xmin ) et son «temps» d'expiration ( xmax ). Les guillemets indiquent qu'un compteur d'incrémentation spécial est utilisé plutôt que l'heure elle-même. Et ce compteur est l'identifiant de transaction .

(Comme d'habitude, en réalité, cela est plus compliqué: l'ID de transaction ne peut pas toujours augmenter en raison d'une profondeur de bits limitée du compteur. Mais nous explorerons plus de détails à ce sujet lorsque notre discussion atteindra le gel.)

Lorsqu'une ligne est créée, la valeur de xmin est définie égale à l'ID de la transaction qui a exécuté la commande INSERT, tandis que xmax n'est pas renseigné.

Lorsqu'une ligne est supprimée, la valeur xmax de la version actuelle est étiquetée avec l'ID de la transaction qui a effectué DELETE.

Une commande UPDATE effectue en fait deux opérations suivantes: DELETE et INSERT. Dans la version actuelle de la ligne, xmax est défini égal à l'ID de la transaction qui a effectué UPDATE. Ensuite, une nouvelle version de la même ligne est créée, dans laquelle la valeur de xmin est identique à xmax de la version précédente.

xmin champs xmin et xmax sont inclus dans l'en-tête d'une version de ligne. En plus de ces champs, l'en-tête de tuple en contient d'autres, tels que:

  • infomask - plusieurs bits qui déterminent les propriétés d'un tuple donné. Il y en a plusieurs, et nous en discuterons au fil du temps.
  • ctid - une référence à la prochaine version, plus récente, de la même ligne. ctid de la ctid de ligne la plus récente et la plus récente fait référence à cette même version. Le nombre est sous la forme (x,y) , où x est le numéro de la page et y est le numéro d'ordre du pointeur dans le tableau.
  • Le bitmap NULL, qui marque les colonnes d'une version donnée qui contiennent un NULL. NULL n'est pas une valeur régulière des types de données, et par conséquent, nous devons stocker cette caractéristique séparément.

Par conséquent, l'en-tête semble assez volumineux: 23 octets par chaque tuple au minimum, mais généralement plus grand en raison de la bitmap NULL. Si une table est «étroite» (c'est-à-dire qu'elle contient peu de colonnes), les octets de surcharge peuvent occuper plus d'espace que les informations utiles.

Insérer


Voyons plus en détail comment les opérations sur les lignes sont effectuées à un niveau bas, et commençons par un insert.

Pour expérimenter, nous allons créer 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); 

Nous commençons une transaction pour insérer une ligne.

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

Voici l'ID de notre transaction en cours:

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

Examinons le contenu de la page. La fonction heap_page_items de l'extension "pageinspect" nous permet d'obtenir 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" dans PostgreSQL désigne des tables. C'est une autre utilisation bizarre d'un terme: un tas est une structure de données connue, qui n'a rien à voir avec une table. Ce mot est utilisé ici dans le sens où "tout est entassé", contrairement aux index ordonnés.

Cette fonction affiche les données "telles quelles", dans un format difficile à comprendre. Pour clarifier les choses, nous ne laissons qu'une partie des informations et les interprétons:

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

Nous avons fait ce qui suit:

  • Ajout d'un zéro au numéro de pointeur pour qu'il ressemble à un t_ctid : (numéro de page, numéro de pointeur).
  • Interprété l'état du pointeur lp_flags . C'est "normal" ici, ce qui signifie que le pointeur fait référence à une version de ligne. Nous discuterons d'autres valeurs plus tard.
  • Jusqu'à présent, nous n'avons sélectionné que deux paires d'informations. xmin_committed bits xmin_committed et xmin_aborted indiquent si la transaction avec l'ID xmin est xmin ( xmin ). Une paire de bits similaires concerne la transaction avec l'ID xmax .

Qu'observons-nous? Lorsqu'une ligne est insérée, dans la page du tableau apparaît un pointeur qui a le numéro 1 et fait référence à la première et la seule version de la ligne.

Le champ xmin dans le tuple est rempli avec l'ID de la transaction en cours. Étant donné que la transaction est toujours active, les bits xmin_committed et xmin_aborted sont non xmin_aborted .

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

Le champ xmax est rempli avec le numéro conventionnel 0 puisque le tuple n'est pas supprimé, c'est-à-dire à jour. Les transactions xmax_aborted ce nombre en raison de l' xmax_aborted bits xmax_aborted .

Passons à l'étape suivante pour améliorer la lisibilité en ajoutant des bits d'information aux ID de transaction. Et créons la 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; 

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

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

Nous pouvons obtenir des informations similaires, mais beaucoup moins détaillées, à partir du tableau lui-même en utilisant des pseudo-colonnes xmin et xmax :

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

Valider


Lorsqu'une transaction réussit, son statut doit être mémorisé, c'est-à-dire que la transaction doit être marquée comme validée. À cette fin, la structure XACT est utilisée. (Avant la version 10, il s'appelait CLOG (journal de validation), et vous êtes toujours susceptible de rencontrer ce nom.)

XACT n'est pas une table du catalogue système, mais des fichiers dans le répertoire PGDATA / pg_xact. Deux bits sont alloués dans ces fichiers pour chaque transaction - "validés" et "abandonnés" - exactement de la même manière que dans l'en-tête de tuple. Ces informations sont réparties sur plusieurs fichiers uniquement pour des raisons de commodité; nous y reviendrons lorsque nous discuterons du gel. PostgreSQL fonctionne avec ces fichiers page par page, comme avec tous les autres.

Ainsi, lorsqu'une transaction est validée, le bit "validé" est défini pour cette transaction dans XACT. Et c'est tout ce qui se passe lorsque la transaction est validée (bien que nous ne mentionnions pas encore le journal d'écriture anticipée).

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

  1. La transaction xmin t-elle xmin effectuée? Sinon, le tuple créé ne doit pas être visible.
    Ceci est vérifié en regardant à travers une autre structure, qui se trouve dans la mémoire partagée de l'instance et appelée ProcArray. Cette structure contient une liste de tous les processus actifs, ainsi que l'ID de la transaction (active) actuelle pour chacun.
  2. Si la transaction a été effectuée, a-t-elle été validée ou annulée? S'il a été annulé, le tuple ne doit pas non plus être visible.
    C'est exactement pour cela que XACT est nécessaire. Mais il est coûteux de vérifier XACT à chaque fois, bien que les dernières pages de XACT soient stockées dans des tampons dans la mémoire partagée. Par conséquent, une fois compris, l'état de la transaction est écrit sur les bits xmin_committed et xmin_aborted du tuple. Si l'un de ces bits est défini, l'état de la transaction est traité comme connu et la prochaine transaction n'aura pas besoin de vérifier XACT.

Pourquoi la transaction qui effectue l'insertion ne définit-elle pas ces bits? Lorsqu'une insertion est en cours, la transaction ne sait pas encore si elle se terminera avec succès. Et au moment de la validation, il est déjà difficile de savoir quelles lignes et dans quelles pages ont été modifiées. Il peut y avoir beaucoup de ces pages, et il n'est pas pratique de les suivre. En outre, certaines pages peuvent être supprimées du disque du cache de tampon; les relire afin de changer les bits signifierait un ralentissement significatif de la validation.

Le revers de l'économie est qu'après les mises à jour, toute transaction (même celle exécutant SELECT) peut commencer à changer les pages de données dans le cache de tampon.

Nous engageons donc le changement.

 => COMMIT; 

Rien n'a changé dans la page (mais nous savons que l'état des transactions est déjà écrit 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, une transaction qui accède d'abord à la page devra déterminer le statut de la transaction xmin et l'écrira 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, l'ID de la transaction de suppression en cours est écrit dans le champ xmax de la version à jour et le bit xmax_aborted est réinitialisé.

Notez que la valeur de xmax correspondant à la transaction active fonctionne comme un verrou de ligne. Si une autre transaction va mettre à jour ou supprimer cette ligne, elle devra attendre la xmax transaction xmax . Nous parlerons des verrous plus en détail plus tard. À ce stade, notez seulement que le nombre de verrous de ligne n'est pas du tout limité. Ils n'occupent pas de mémoire et les performances du système ne sont pas affectées par ce nombre. Cependant, les transactions de longue durée présentent d'autres inconvénients, qui seront également abordés plus loin.

Supprimons une ligne.

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

Nous voyons que l'ID 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) 

Abandonner


L'abandon d'une transaction fonctionne de manière similaire pour la validation, sauf que le bit "abandonné" est défini dans XACT. Un abandon se fait aussi rapidement qu'un commit. Bien que la commande soit appelée ROLLBACK, les modifications ne sont pas annulées: tout ce que la transaction a déjà changé, reste intact.

 => 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. Bien que le nombre xmax lui-même soit toujours dans la page, il ne sera pas examiné.

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


Une mise à jour fonctionne comme si la version actuelle était supprimée en premier, puis une nouvelle était insérée.

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

La requête renvoie une ligne (la nouvelle version):

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

Mais nous pouvons voir les deux versions sur la page:

 => 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 étiquetée avec l'ID de la transaction en cours dans le champ xmax . De plus, cette valeur a écrasé l'ancienne depuis l'annulation de la transaction précédente. 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, en tant que nouvelle.

La page d'index contient désormais le deuxième pointeur et la deuxième ligne, qui fait référence à la deuxième version dans la page du tableau.

De la même manière que pour une suppression, la valeur de xmax dans la première version indique que la ligne est verrouillée.

Enfin, nous validons la transaction.

 => COMMIT; 

Index


Jusqu'à présent, nous ne parlions que de pages de table. Mais que se passe-t-il à l'intérieur des index?

Les informations dans les pages d'index dépendent fortement du type d'index spécifique. De plus, même un type d'index peut avoir différents types de pages. Par exemple: un arbre B a la page de métadonnées et les pages "normales".

Néanmoins, une page d'index a généralement un tableau de pointeurs vers les lignes et les lignes elles-mêmes (tout comme les pages de table). De plus, un espace à la fin d'une page est alloué aux données spéciales.

Les lignes dans les index peuvent également avoir des structures différentes selon le type d'index. Par exemple: dans un arbre B, les lignes pertinentes pour les pages feuilles contiennent la valeur de la clé d'indexation et une référence ( ctid ) à la ligne de table appropriée. En général, un indice peut être structuré de manière très différente.

Le point principal est que dans les index de tout type, il n'y a pas de versions de lignes. Ou nous pouvons considérer que chaque ligne est représentée par une seule version. En d'autres termes, l'en-tête de la ligne d'index ne contient pas les champs xmin et xmax . Pour l'instant, nous pouvons supposer que les références du point d'index à toutes les versions des lignes de table. Donc, pour savoir quelles versions de ligne sont visibles pour une transaction, PostgreSQL doit regarder dans le tableau. (Comme d'habitude, ce n'est pas toute l'histoire. Parfois, la carte de visibilité permet d'optimiser le processus, mais nous en discuterons plus tard.)

Ici, dans la page d'index, nous trouvons des pointeurs vers les deux versions: la mise à jour et la précédente:

 => 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 profite d'une optimisation qui permet de dépenser "avec parcimonie" les ID de transaction.

Si une transaction ne lit que des données, cela n'affecte en rien la visibilité du tuple. Par conséquent, le processus backend attribue d'abord un ID virtuel (xid virtuel) à la transaction. Cet ID se compose de l'identifiant du processus et d'un numéro séquentiel.

L'attribution de cet ID virtuel ne nécessite pas de synchronisation entre tous les processus et se fait donc très rapidement. Nous découvrirons une autre raison d'utiliser des identifiants virtuels lorsque nous discuterons du gel.

Les instantanés de données ne prennent pas du tout en compte l'ID virtuel.

À différents moments, le système peut avoir des transactions virtuelles avec des identifiants déjà utilisés, et c'est très bien. Mais cet ID ne peut pas être écrit sur les pages de données car lorsque la page est accédée la prochaine fois, l'ID peut devenir vide de sens.

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

Mais si une transaction commence à modifier des données, elle reçoit un ID de transaction vrai et unique.

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

 => COMMIT; 

Sous-transactions


Points de sauvegarde


En SQL, des points de sauvegarde sont définis, ce qui permet d'annuler certaines opérations de la transaction sans son avortement complet. Mais cela est incompatible avec le modèle ci-dessus car le statut de la transaction est un pour toutes les modifications et aucune donnée n'est physiquement annulée.

Pour implémenter cette fonctionnalité, une transaction avec un point de sauvegarde est divisée en plusieurs sous- transactions distinctes dont les statuts peuvent être gérés séparément.

Les sous-transactions ont leurs propres ID (supérieurs à l'ID de la transaction principale). Les statuts des sous-transactions sont écrits dans XACT de manière habituelle, mais le statut final dépend du statut de la transaction principale: si elle est annulée, toutes les sous-transactions sont également annulées.

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

Ne confondez pas les sous-transactions avec les transactions autonomes. Les transactions autonomes ne dépendent en aucun cas les unes des autres, tandis que les sous-transactions dépendent. Il n'y a pas de transactions autonomes dans PostgreSQL ordinaire, ce qui est peut-être pour le mieux: elles sont en fait extrêmement rarement nécessaires, et leur disponibilité dans d'autres SGBD invite à des abus, dont tout le monde souffre.

Effaçons le tableau, démarrons une transaction et insérons une 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, nous établissons 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 l'ID de la transaction principale plutôt que de la sous-transaction.

 => 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 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 qui a été ajoutée par la sous-transaction annulée.

Validation des modifications.

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

On voit clairement maintenant que chaque sous-transaction a son propre statut.

Notez que SQL n'autorise pas l'utilisation explicite des sous-transactions, c'est-à-dire que vous ne pouvez pas démarrer une nouvelle transaction avant d'avoir terminé la transaction en cours. Cette technique est implicitement impliquée lorsque des points d'enregistrement sont utilisés et également lors de la gestion des exceptions PL / pgSQL, ainsi que dans d'autres situations 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é de fonctionnement


Que se passe-t-il si une erreur se produit pendant l'exécution de 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 est survenue. Maintenant, la transaction est traité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 nous essayons de valider les modifications, PostgreSQL signalera la restauration:

 => COMMIT; 
 ROLLBACK 

Pourquoi est-il impossible de poursuivre l'exécution de la transaction après un échec? Le fait est que l'erreur pourrait se produire afin que nous ayons accès à une partie des modifications, c'est-à-dire que l'atomicité serait rompue non seulement pour la transaction, mais même pour un seul opérateur. Par exemple, dans notre exemple, l'opérateur aurait pu mettre à jour une ligne avant que l'erreur ne se produise:

 => 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 convient de noter que psql dispose d'un mode qui permet de poursuivre la transaction après l'échec, comme si les effets de l'opérateur erroné étaient annulés.

 => \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 comprendre que dans ce mode, psql établit en fait un point de sauvegarde implicite avant chaque commande et lance un retour en arrière en cas d'échec. Ce mode n'est pas utilisé par défaut, car l'établissement de points de sauvegarde (même sans retour en arrière) entraîne une surcharge importante.

Continuez à lire .

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


All Articles