Ainsi, nous avons abordé les questions liées à , et fait une digression sur . Et enfin, nous sommes arrivés à la partie la plus intéressante : les versions des lignes.
L'en-tĂȘte
Comme nous l'avons déjà mentionné, chaque ligne peut exister simultanément dans la base de données sous plusieurs versions. Il faut distinguer une version de l'autre. à cette fin, chaque version a deux marqueurs qui définissent le « temps » d'application de cette version (xmin et xmax). Entre guillemets, car on n'utilise pas le temps à proprement parler, mais un compteur spécial incrémental. Et ce compteur est le numéro de transaction.
(Comme d'habitude, en rĂ©alitĂ© tout est plus complexe : le numĂ©ro de transaction ne peut pas toujours ĂȘtre croissant en raison de la limitation de la taille du compteur. Mais nous aborderons ces dĂ©tails en profondeur lorsque nous traiterons de la congĂ©lation.)
Lorsqu'une ligne est créée, la valeur de xmin est définie par le numéro de la transaction qui a exécuté la commande INSERT, et xmax n'est pas rempli.
Lorsque la ligne est supprimée, la valeur de xmax de la version actuelle est marquée par le numéro de la transaction qui a exécuté DELETE.
Lorsqu'une ligne est modifiĂ©e par la commande UPDATE, deux opĂ©rations sont en fait effectuĂ©es : DELETE et INSERT. Dans la version actuelle de la ligne, xmax est dĂ©fini sur le numĂ©ro de transaction qui a exĂ©cutĂ© UPDATE. Ensuite, une nouvelle version de la mĂȘme ligne est créée ; sa valeur de xmin correspond Ă la valeur de xmax de la version prĂ©cĂ©dente.
Les champs xmin et xmax font partie de l'en-tĂȘte de la version de la ligne. En plus de ces champs, l'en-tĂȘte contient d'autres informations, par exemple :
- infomask â une sĂ©rie de bits dĂ©finissant les propriĂ©tĂ©s de cette version. Il y en a pas mal ; nous examinerons progressivement les principales.
- ctid â un lien vers la version suivante, plus rĂ©cente de la mĂȘme ligne. Pour la version la plus rĂ©cente, ctid pointe vers elle-mĂȘme. Le numĂ©ro est de la forme (x,y), oĂč x est le numĂ©ro de la page et y est le numĂ©ro ordinal du pointeur dans le tableau.
- carte des valeurs indĂ©finies â marque les colonnes de cette version qui contiennent une valeur indĂ©finie (NULL). NULL n'est pas l'une des valeurs habituelles des types de donnĂ©es, c'est pourquoi il faut stocker ce critĂšre sĂ©parĂ©ment.
En consĂ©quence, l'en-tĂȘte devient assez volumineux â au moins 23 octets pour chaque version de ligne, et gĂ©nĂ©ralement plus en raison de la carte des NULL. Si la table est « Ă©troite » (c'est-Ă -dire contient peu de colonnes), les frais gĂ©nĂ©raux peuvent dĂ©passer l'information utile.
Insertion
Examinons en détail comment les opérations sur les chaßnes se déroulent à un niveau bas, en commençant par l'insertion.
Pour nos 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 commencé la transaction.
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
Voici le numéro de notre transaction actuelle :
=> SELECT txid_current();
txid_current
--------------
3664
(1 ligne)
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 des lignes :
=> SELECT * FROM heap_page_items(get_raw_page('t',0)) gx
-[ ENREGISTREMENT 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
Notons que par le terme heap (tas) dans PostgreSQL, nous faisons rĂ©fĂ©rence aux tables. C'est une autre utilisation Ă©trange du terme â le tas est une , qui n'a rien Ă voir avec une table. Ici, ce mot est utilisĂ© dans le sens de « tout est entassĂ© », par opposition aux index ordonnĂ©s.
La fonction montre les données « telles quelles », dans un format difficile à interpréter. Pour comprendre, nous allons conserver seulement une partie de l'information et la déchiffrer :
=> 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
-[ ENREGISTREMENT 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 du pointeur pour le mettre dans le mĂȘme format que t_ctid : (numĂ©ro de page, numĂ©ro de pointeur).
- Nous avons déchiffré l'état du pointeur lp_flags. Ici, il est « normal » - cela signifie que le pointeur fait effectivement référence à la version de la ligne. Nous examinerons d'autres valeurs plus tard.
- Parmi tous les bits d'information, nous avons pour l'instant isolé seulement deux paires. Les bits xmin_committed et xmin_aborted indiquent si la transaction numérotée xmin a été validée (annulée). Deux bits similaires concernent la transaction numérotée xmax.
Que voyons-nous ? Lors de l'insertion d'une ligne dans une page de table, un pointeur apparaßtra avec le numéro 1, faisant référence à la premiÚre et unique version de la ligne.
Dans la version de la ligne, le champ xmin est rempli avec le numéro de la transaction actuelle. La transaction est encore active, donc les deux bits xmin_committed et xmin_aborted ne sont pas définis.
Le champ ctid de la version de la ligne fait rĂ©fĂ©rence Ă cette mĂȘme ligne. Cela signifie qu'aucune version plus rĂ©cente n'existe.
Le champ xmax est rempli avec le numĂ©ro fictif 0, car cette version de la ligne n'est pas supprimĂ©e et est actuelle. Les transactions ne prĂȘteront pas attention Ă ce numĂ©ro, 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, car cette requĂȘte nous sera nĂ©cessaire 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, il est beaucoup plus clair de voir ce qui se passe dans l'en-tĂȘte de la version de la ligne :
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)
On peut obtenir des informations similaires, mais beaucoup moins détaillées, directement à partir de la table en utilisant les pseudocolonnes xmin et xmax :
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 row)
Validation
Ă la fin rĂ©ussie d'une transaction, il est nĂ©cessaire de mĂ©moriser son statut â de marquer qu'elle a Ă©tĂ© validĂ©e. Pour cela, on utilise une structure appelĂ©e XACT (et auparavant, jusqu'Ă la version 10, elle Ă©tait appelĂ©e CLOG (commit log) et ce terme peut encore apparaĂźtre Ă divers endroits).
XACT n'est pas une table de catalogue systĂšme ; ce sont des fichiers dans le rĂ©pertoire PGDATA/pg_xact. Pour chaque transaction, deux bits sont allouĂ©s : committed et aborted â tout comme dans l'en-tĂȘte de la version de la ligne. Ces informations sont rĂ©parties sur plusieurs fichiers uniquement pour des raisons de commoditĂ©, nous reviendrons sur ce sujet lorsque nous examinerons la congĂ©lation. Et la manipulation de ces fichiers se fait page par page, comme pour tous les autres.
Ainsi, lors de l'engagement d'une transaction dans XACT, le bit "committed" est défini pour cette transaction. C'est tout ce qui se passe lors de l'engagement (pour l'instant, nous ne parlons pas du journal de pré-écriture).
Lorsque toute autre transaction accÚde à la page de la table que nous venons de voir, elle devra répondre à plusieurs questions.
- La transaction a-t-elle Ă©tĂ© complĂ©tĂ©e ? Si ce n'est pas le cas, la version de la ligne créée ne doit pas ĂȘtre visible.
Cette vĂ©rification est effectuĂ©e en consultant une autre structure qui se trouve dans la mĂ©moire partagĂ©e de l'instance et qui s'appelle ProcArray. Elle contient la liste de tous les processus actifs, et pour chacun est indiquĂ© le numĂ©ro de sa transaction actuelle (active). - Si elle a Ă©tĂ© complĂ©tĂ©e, comment â par un engagement ou un rollback ? Si par un rollback, alors la version de la ligne ne doit Ă©galement pas ĂȘtre visible.
C'est justement pour cela que XACT est nécessaire. Cependant, bien que les derniÚres pages de XACT soient conservées dans des buffers en mémoire vive, vérifier XACT à chaque fois serait coûteux. Par conséquent, le statut de la transaction, une fois déterminé, est enregistré dans les bits xmin_committed et xmin_aborted de la version de la ligne. Si l'un de ces bits est activé, l'état de la transaction xmin est considéré comme connu et la transaction suivante n'aura plus à consulter XACT.
Pourquoi ces bits ne sont-ils pas définis par la transaction qui effectue l'insertion ? Lors d'une insertion, la transaction ne sait pas encore si elle se terminera avec succÚs. Et au moment de l'engagement, il n'est déjà pas clair quelles lignes dans quelles pages ont été modifiées. Il peut y avoir beaucoup de telles pages, et il serait peu rentable de les mémoriser. De plus, certaines pages peuvent avoir été évincées du cache tampon sur le disque ; les lire à nouveau pour modifier les bits signifierait ralentir considérablement l'engagement.
Le revers de l'Ă©conomie est que, 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.
Ainsi, engageons le changement.
=> COMMIT;
Rien n'a changé dans la page (mais nous savons 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)
Maintenant, la transaction qui a accédé en premier à la page devra déterminer le statut de la transaction xmin et l'enregistrera dans les bits d'information :
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 ligne)
=> SELECT * FROM heap_page('t',0);
ctid | état | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 ligne)
Désinstallation
Lors de la suppression d'une ligne, le champ xmax de la version actuelle enregistre le numéro de la transaction de suppression en cours, tandis que le bit xmax_aborted est réinitialisé.
Notons que la valeur xmax, correspondant à la transaction active, agit comme un verrouillage de ligne. Si une autre transaction tente 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 verrouillages plus tard. Pour l'instant, notons simplement qu'il n'y a aucune limite au nombre de verrouillages de lignes. Ils n'occupent pas d'espace dans la mémoire vive et la performance du systÚme ne souffre pas de leur nombre. Cependant, les transactions "longues" présentent d'autres inconvénients, mais nous en parlerons également plus tard.
Supprimons la ligne.
=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
txid_current
--------------
3665
(1 row)
Nous voyons que le numéro de transaction a été enregistré dans le champ xmax, mais que les bits d'information ne sont pas activés :
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)
Annulation
L'annulation des modifications fonctionne de la mĂȘme maniĂšre que le commit, sauf que dans XACT pour la transaction, le bit aborted est dĂ©fini. L'annulation s'exĂ©cute aussi rapidement que le commit. Bien que la commande soit appelĂ©e ROLLBACK, les changements ne sont pas annulĂ©s : tout ce que la transaction a rĂ©ussi Ă modifier dans les pages de donnĂ©es reste inaltĂ©rĂ©.
=> 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, le statut sera vérifié et le bit de l'indice xmax_aborted sera défini dans la version de la ligne. Le numéro xmax reste sur la page, mais personne ne le regardera désormais.
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 ligne)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 row)
Mise Ă jour
La mise à jour fonctionne comme si la suppression de la version actuelle de la ligne avait été effectuée, suivie de l'insertion d'une nouvelle.
=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
txid_current
--------------
3666
(1 row)
La requĂȘte retourne une ligne (la 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 par le numéro de la transaction en cours dans le champ xmax. De plus, cette valeur est écrite par-dessus 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 actuelle est encore inconnu.
La premiÚre version de la ligne fait maintenant référence à la seconde (champ t_ctid), comme à une version plus récente.
Dans la page d'index, un deuxiÚme pointeur et une deuxiÚme ligne apparaissent, se référant à la deuxiÚme version dans la page de table.
Tout comme lors de la suppression, la valeur xmax dans la premiÚre version de la ligne indique que la ligne est verrouillée.
Et nous terminerons la transaction.
=> COMMIT;
Indices
Jusqu'à présent, nous avons uniquement parlé des pages de table. Que se passe-t-il à l'intérieur des indices ?
Les informations dans les pages d'index dĂ©pendent fortement du type spĂ©cifique d'index. MĂȘme au sein d'un mĂȘme type d'index, il peut y avoir diffĂ©rents types de pages. Par exemple, un arbre B contient une page de mĂ©tadonnĂ©es et des pages « ordinaires ».
Cependant, en gĂ©nĂ©ral, une page contient un tableau de pointeurs vers des lignes et les lignes elles-mĂȘmes (tout comme dans une page de table). De plus, Ă la fin de la page, de l'espace est rĂ©servĂ© pour des donnĂ©es spĂ©ciales.
Les lignes dans les indices peuvent Ă©galement avoir des structures trĂšs variĂ©es 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'indexation et un lien (ctid) vers la ligne correspondante de la table. En gĂ©nĂ©ral, un index peut ĂȘtre structurĂ© de maniĂšre complĂštement diffĂ©rente.
Le point le plus important est que dans les indices de tout type, il n'y a pas de versions de lignes. Ou l'on peut considĂ©rer que chaque ligne est reprĂ©sentĂ©e par exactement une version. En d'autres termes, dans l'en-tĂȘte de la ligne d'index, il n'y a pas de champs xmin et xmax. On peut considĂ©rer que les liens dans l'index pointent vers toutes les versions de table des lignes â donc pour comprendre laquelle des versions une transaction verra, il faut consulter la table. (Comme d'habitude, ce n'est pas toute la vĂ©ritĂ©. Dans certains cas, la carte de visibilitĂ© permet d'optimiser le processus, mais nous en discuterons plus en dĂ©tail plus tard.)
Dans la page d'index, nous trouvons des pointeurs vers les deux versions, Ă la fois la actuelle et l'ancienne :
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 lignes)
Transactions virtuelles
En pratique, PostgreSQL utilise une optimisation qui permet de « sauver » les numéros de transaction.
Lorsqu'une transaction ne fait que lire des données, elle n'influence en rien la visibilité des versions des lignes. Par conséquent, au début, le processus de service attribue un numéro virtuel à la transaction (virtual xid). Ce numéro est composé d'un identifiant de processus et d'un numéro séquentiel.
L'attribution de ce numéro ne nécessite pas de synchronisation entre tous les processus et s'effectue donc trÚs rapidement. La deuxiÚme raison d'utiliser des numéros virtuels sera abordée lorsque nous parlerons de la suspension.
Les numéros virtuels ne sont pas pris en compte dans les clichés de données.
Ă diffĂ©rents moments, le systĂšme peut contenir des transactions virtuelles avec des numĂ©ros dĂ©jĂ utilisĂ©s, ce qui est normal. Mais un tel numĂ©ro ne peut pas ĂȘtre enregistrĂ© dans les pages de donnĂ©es, car lors de la prochaine rĂ©fĂ©rence Ă la page, il peut perdre toute signification.
=> DĂBUT;
=> SĂLECTIONNER txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 ligne)
Si une transaction commence à modifier des données, elle reçoit un véritable numéro de transaction unique.
=> METTRE Ă JOUR comptes SET montant = montant - 1,00;
=> SĂLECTIONNER txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 ligne)
=> COMMIT;
Transactions imbriquées
Points de sauvegarde
En SQL, sont définis points de sauvegarde (savepoint), qui permettent d'annuler une partie des opérations d'une transaction sans l'interrompre complÚtement. Cependant, cela ne s'intÚgre pas dans le schéma ci-dessus, car le statut d'une transaction est unique pour tous ses changements, et aucune donnée n'est physiquement annulée.
Pour rĂ©aliser une telle fonctionnalitĂ©, la transaction avec un point de sauvegarde est divisĂ©e en plusieurs transactions imbriquĂ©es (subtransaction), dont le statut peut ĂȘtre gĂ©rĂ© indĂ©pendamment.
Les transactions imbriquées ont leur propre numéro (plus grand que le numéro de la transaction principale). Le statut des transactions imbriquées est enregistré de maniÚre habituelle dans XACT, mais le statut final dépend de celui de la transaction principale : si elle est annulée, alors toutes les transactions imbriquées le sont également.
Les informations sur l'imbrication des transactions sont stockĂ©es dans des fichiers dans le rĂ©pertoire PGDATA/pg_subtrans. L'accĂšs aux fichiers se fait par l'intermĂ©diaire de buffers en mĂ©moire partagĂ©e de l'instance, organisĂ©s de la mĂȘme maniĂšre que les buffers XACT.
Ne confondez pas les transactions imbriquées et les transactions autonomes. Les transactions autonomes ne dépendent pas les unes des autres, alors que les imbriquées le font. Il n'y a pas de transactions autonomes dans PostgreSQL standard, et c'est probablement mieux ainsi : elles ne sont nécessaires que trÚs rarement, et leur présence dans d'autres systÚmes de gestion de bases de données peut provoquer des abus dont tout le monde souffre par la suite.
Nous allons vider la table, commencer la transaction et insérer une ligne :
=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 ligne)
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 ligne)
=> SELECT * FROM heap_page('t',0);
ctid | état | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(1 ligne)
Maintenant, nous allons définir un point de sauvegarde et insérer une autre ligne.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 ligne)
Notez que la fonction txid_current() retourne le numéro de la transaction principale, et non celle imbriquée.
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 lignes)
=> SELECT * FROM heap_page('t',0);
ctid | état | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 | 0 (a) | (0,2)
(2 lignes)
Revenons au point de sauvegarde et insérons une 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 lignes)
=> SELECT * FROM heap_page('t',0);
ctid | état | 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 lignes)
Sur la page, nous continuons à voir la ligne ajoutée par la transaction imbriquée annulée.
Validons les modifications.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 lignes)
=> SELECT * FROM heap_page('t',0);
ctid | état | 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 lignes)
Il est maintenant clair que chaque transaction imbriquée a son propre état.
Notons que les transactions imbriquĂ©es ne peuvent pas ĂȘtre utilisĂ©es explicitement dans SQL, c'est-Ă -dire qu'il n'est pas possible de commencer une nouvelle transaction sans avoir terminĂ© l'actuelle. Ce mĂ©canisme est utilisĂ© implicitement lors de l'utilisation des points de sauvegarde, ainsi que lors du traitement d'exceptions PL/pgSQL et dans certains autres cas plus exotiques.
=> BEGIN;
BEGIN
=> BEGIN;
AVERTISSEMENT : il y a déjà une transaction en cours
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
AVERTISSEMENT : il n'y a pas de transaction en cours
COMMIT
Erreurs et atomicité des opérations
Que se passera-t-il si une erreur se produit lors de l'exécution d'une opération ? Par exemple, comme suit :
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 lignes)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERREUR : division par zéro
Une erreur s'est produite. La transaction est maintenant considĂ©rĂ©e comme interrompue et aucune opĂ©ration ne peut ĂȘtre effectuĂ©e dans celle-ci :
=> SELECT * FROM t;
ERREUR : la transaction actuelle est annulée, les commandes sont ignorées jusqu'à la fin du bloc de transaction
Et mĂȘme si vous essayez de valider les modifications, PostgreSQL indiquera une annulation :
=> COMMIT;
ROLLBACK
Pourquoi ne peut-on pas continuer l'exĂ©cution d'une transaction aprĂšs un Ă©chec ? En fait, l'erreur aurait pu survenir de telle maniĂšre que nous aurions accĂšs Ă une partie des modifications â l'atome aurait Ă©tĂ© rompu, mĂȘme pas la transaction, mais l'opĂ©rateur. 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 lignes)
Il convient de noter qu'il existe un mode dans psql qui permet néanmoins de continuer l'exécution de la transaction aprÚs une erreur, 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 lignes)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERREUR : division par zéro
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 lignes)
=> COMMIT;
Il n'est pas difficile de deviner que dans ce mode, psql place effectivement un point de sauvegarde implicite avant chaque commande, et en cas d'Ă©chec, initie un retour Ă ce point. Ce mode n'est pas activĂ© par dĂ©faut, car la mise en place de points de sauvegarde (mĂȘme sans retour) entraĂźne des coĂ»ts opĂ©rationnels significatifs.
Source : habr.com
