37. PostgreSQL : Transactions et session
Ce chapitre décrit, pour une instance MIRAJ en langage PostgreSQL :
- le contrôle des transactions (
BEGIN,COMMIT,ROLLBACK,SAVEPOINT) et ce qui se passe après une erreur dans un bloc de transaction ; - les instructions de définition (
CREATE TABLE,DROP TABLE…) dans un bloc de transaction ; - les niveaux d'isolement et le modèle de concurrence de MIRAJ : une isolation sérialisable sans verrou de ligne, où un conflit se règle en recommençant la transaction ;
- les lectures verrouillantes (
SELECT … FOR UPDATE),LOCK TABLEet les verrous consultatifs ; - les paramètres de session (
SET,SHOW,RESET), le chemin de recherche (search_path), le fuseau horaire et le délai d'instruction ; - les curseurs SQL, les instructions préparées et la remise à zéro d'une session.
Les exemples utilisent le schéma d'exemple ventes (tables client, article, commande, ligne_commande) créé et rempli au chapitre 31. Prise en main : trois clients, trois articles (stocks 500, 200 et 80), trois commandes. Les résultats montrés sont ceux qu'affiche un client PostgreSQL : étiquette de l'instruction (BEGIN, UPDATE 1…), lignes, ou erreur avec son SQLSTATE. Les exemples à deux sessions se lisent de haut en bas, la session A à gauche, la session B à droite.
37.1 Auto-validation et transactions explicites#
Auto-validation#
Hors d'un bloc de transaction, chaque instruction forme sa propre transaction, validée dès qu'elle réussit et annulée en entier si elle échoue. C'est le comportement de PostgreSQL, et celui qu'attendent psql et les pilotes.
Une requête qui contient plusieurs instructions séparées par ; (envoyées en une fois par psql -c ou par un pilote) forme une transaction implicite, comme dans PostgreSQL : si l'une échoue, les écritures des instructions précédentes sont annulées et les suivantes ne sont pas exécutées.
UPDATE article SET stock = stock - 1 WHERE id = 1; SELECT 1/0; UPDATE article SET stock = stock - 1 WHERE id = 2UPDATE 1
ERROR: division by zero (22012)Après quoi les stocks des articles 1 et 2 sont toujours 500 et 200. Un COMMIT écrit dans la requête valide ce qui le précède (UPDATE …; COMMIT; SELECT 1/0 garde l'UPDATE), et un BEGIN y ouvre un bloc ordinaire, qui reste ouvert après la requête s'il n'est pas fermé. Une instruction de définition (CREATE TABLE…) de la requête n'est pas défaite par l'échec d'une instruction qui la suit.
Ouvrir une transaction#
BEGIN [WORK | TRANSACTION] [mode [, mode] …];
START TRANSACTION [mode [, mode] …];où mode est :
ISOLATION LEVEL {SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED}
READ WRITE | READ ONLY
[NOT] DEFERRABLELes modes s'écrivent séparés par une virgule ou par une espace. [NOT] DEFERRABLE est accepté sans effet. Le niveau d'isolement est décrit au 37.3.
BEGIN;
INSERT INTO commande (client_id) VALUES (3) RETURNING id;
INSERT INTO ligne_commande VALUES (4, 1, 10, 1.20);
UPDATE article SET stock = stock - 10 WHERE id = 1;
COMMIT;BEGIN
id
----
4
(1 ligne)
INSERT 0 1
INSERT 0 1
UPDATE 1
COMMITUn BEGIN envoyé alors qu'une transaction est déjà ouverte est accepté sans erreur ni avis, et la transaction en cours continue.
Valider ou annuler#
COMMIT [WORK | TRANSACTION] [AND [NO] CHAIN];
END [WORK | TRANSACTION];
ROLLBACK [WORK | TRANSACTION] [AND [NO] CHAIN];
ABORT;COMMIT(ouEND) valide toutes les écritures de la transaction : elles deviennent visibles des autres sessions et durables.ROLLBACK(ouABORT) les annule toutes : chaque ligne reprend sa valeur d'avant la transaction.AND CHAINouvre aussitôt une nouvelle transaction, avec le même mode (READ ONLYouREAD WRITE) ;AND NO CHAINest la valeur par défaut.COMMITouROLLBACKhors de toute transaction sont acceptés sans erreur ni avis.
BEGIN;
UPDATE article SET stock = stock - 100 WHERE id = 1;
SELECT stock FROM article WHERE id = 1; -- 400 : la transaction voit ses écritures
ROLLBACK;
SELECT stock FROM article WHERE id = 1; -- 500| Instruction | Étiquette rendue |
|---|---|
BEGIN, BEGIN WORK, BEGIN TRANSACTION | BEGIN |
START TRANSACTION | START TRANSACTION |
COMMIT, END | COMMIT (ROLLBACK si le bloc était en échec) |
ROLLBACK, ABORT, ROLLBACK TO SAVEPOINT | ROLLBACK |
SAVEPOINT, RELEASE | SAVEPOINT, RELEASE |
Erreur dans un bloc de transaction#
Comme dans PostgreSQL, une erreur met le bloc en échec : jusqu'à sa fin, toute instruction reçoit 25P02. Seuls ROLLBACK, ROLLBACK TO SAVEPOINT (voir 37.2) et COMMIT — qui annule alors, et rend l'étiquette ROLLBACK — sont acceptés.
BEGIN;
SELECT 1/0;
SELECT 1;
COMMIT;BEGIN
ERROR: division by zero (22012)
ERROR: current transaction is aborted, commands ignored until end of transaction block (25P02)
ROLLBACKUn conflit de concurrence (SQLSTATE 40001) ou un interblocage (40P01), voir 37.4, met lui aussi le bloc en échec, mais il a déjà annulé la transaction entière : ROLLBACK TO SAVEPOINT ne la rattrape pas, seul un ROLLBACK (ou un COMMIT, qui rend ROLLBACK) termine le bloc.
Transaction en lecture seule#
READ ONLY interdit toute écriture dans la transaction (25006), et l'erreur met le bloc en échec comme toute autre :
BEGIN READ ONLY;
SELECT count(*) FROM client; -- 3
UPDATE client SET ville = 'x' WHERE id = 1;
ROLLBACK;ERROR: Cannot execute statement in a READ ONLY transaction. (25006)SHOW transaction_read_only rend on dans une telle transaction, off sinon.
Instructions de définition dans une transaction#
Les instructions de définition les plus courantes s'exécutent dans la transaction du bloc : ROLLBACK les défait, COMMIT les garde.
| Instruction | Annulable avec la transaction |
|---|---|
CREATE TABLE | oui, sauf CREATE TABLE … AS SELECT et une table temporaire |
CREATE [UNIQUE] INDEX, ALTER TABLE … ADD COLUMN (sans REFERENCES), ALTER TABLE … ADD CONSTRAINT nom UNIQUE (…) | oui, sur une table que la transaction n'a pas encore écrite |
DROP TABLE | oui, sans CASCADE, pour une table dont ne dépend ni vue ni clé étrangère d'une autre table, et que la transaction n'a pas écrite (une table créée par la transaction elle-même se supprime toujours ainsi) |
BEGIN;
CREATE TABLE remise (code varchar(20) PRIMARY KEY, taux numeric(4,2));
ALTER TABLE article ADD COLUMN poids integer;
CREATE INDEX idx_article_stock ON article (stock);
ROLLBACK;
SELECT count(*) FROM remise;
SELECT poids FROM article;BEGIN
CREATE TABLE
ALTER TABLE
CREATE INDEX
ROLLBACK
ERROR: relation "remise" does not exist (42P01)
ERROR: Unknown column 'poids' in 'field list' (42703)ROLLBACK TO SAVEPOINTdéfait de même celles exécutées depuis le point (37.2) ; unCOMMITen échec et la fermeture de la session sansCOMMITles défont aussi.- Elles sont appliquées tout de suite : les autres sessions voient la table créée ou la colonne ajoutée avant le
COMMIT. TRUNCATEdans un bloc est annulable lui aussi (34. Langage DML).- Toute autre instruction de définition (
CREATE VIEW,ALTER TABLE … RENAME,DROP COLUMN,CREATE FUNCTION…), ou l'une de celles du tableau hors des conditions dites, valide d'abord la transaction en cours, avec ses écritures et ses instructions de définition annulables, puis s'exécute ; unROLLBACKqui suit n'a plus rien à défaire :
BEGIN;
INSERT INTO client (nom, email) VALUES ('Paul Leroy', '[email protected]');
CREATE VIEW v_ville AS SELECT DISTINCT ville FROM client; -- valide l'INSERT qui précède
ROLLBACK; -- n'annule plus rien
SELECT nom FROM client WHERE nom = 'Paul Leroy'; -- la ligne est là, la vue aussiUn script de migration qui emploie d'autres instructions que celles du tableau prévoit donc les instructions inverses, ou une sauvegarde (40. Catalogue et outils).
Ce qui ne dépend pas de la transaction#
- Une séquence : une valeur servie par
nextvaln'est jamais rendue parROLLBACK. Il en va de même du compteur d'une colonneserialou d'identité : unINSERTannulé laisse un trou. - Les verrous consultatifs de session (
pg_advisory_lock, voir 37.6) : niCOMMITniROLLBACKne les rendent.
Les paramètres de session, eux, suivent la transaction : un SET fait dans une transaction annulée est défait (voir 37.7).
Variable autocommit#
Extension de MIRAJ, utile aux outils qui ne savent pas ouvrir de bloc : SET autocommit = 0 fait ouvrir une transaction par la première instruction qui suit, jusqu'au prochain COMMIT ou ROLLBACK. SET autocommit = 1 (ou TO on) revient au mode par défaut. Les pilotes PostgreSQL n'en ont pas besoin : leur propre mode « autocommit » (psycopg2 connection.autocommit, JDBC setAutoCommit) envoie simplement des BEGIN et des COMMIT.
37.2 Points de sauvegarde#
SAVEPOINT nom;
ROLLBACK [WORK | TRANSACTION] TO [SAVEPOINT] nom;
RELEASE [SAVEPOINT] nom;SAVEPOINT nompose un point de reprise dans la transaction ouverte.ROLLBACK TO nomdéfait tout ce que la transaction a écrit depuis ce point, retire les points posés après lui, et garde le point lui-même ainsi que la transaction. C'est aussi le moyen de sortir un bloc de l'état d'échec sans tout perdre.RELEASE nomretire le point et ceux posés après lui, sans rien défaire.COMMITetROLLBACKeffacent tous les points.
BEGIN;
INSERT INTO client (nom, email, ville) VALUES ('Léa Petit', '[email protected]', 'Lille');
SAVEPOINT avant_commande;
INSERT INTO commande (client_id) VALUES (99); -- client inconnu
ROLLBACK TO SAVEPOINT avant_commande; -- le bloc redevient utilisable
INSERT INTO commande (client_id) VALUES (4);
COMMIT; -- le client et la commande sont écritsBEGIN
INSERT 0 1
SAVEPOINT
ERROR: Cannot add or update a child row: a foreign key constraint fails ("ventes"."commande",
CONSTRAINT "commande_ibfk_1" FOREIGN KEY ("client_id") REFERENCES "client" ("id")) (23503)
ROLLBACK
INSERT 0 1
COMMITComme dans PostgreSQL :
- des points de même nom s'empilent :
ROLLBACK TOetRELEASEvisent le plus récent, et l'ancien redevient visible une fois le nouveau relâché ; - un nom inconnu donne
savepoint "nom" does not exist(3B001) ; - hors d'un bloc de transaction,
SAVEPOINT,RELEASEetROLLBACK TOsont refusés (25P01,SAVEPOINT can only be used in transaction blocks, de mêmeRELEASE SAVEPOINT …etROLLBACK TO SAVEPOINT …) ; ROLLBACK TOdéfait aussi lesSETfaits depuis le point (37.7), et les instructions de définition annulables exécutées depuis le point (37.1) ;- après une erreur de la classe
40(conflit, interblocage), la transaction est déjà annulée en entier :ROLLBACK TOest refusé (25P01) et le bloc se termine parROLLBACK.
Différences avec PostgreSQL :
- les lignes tenues depuis le point (
FOR UPDATE) restent tenues jusqu'à la fin de la transaction ; - édition Cluster :
SAVEPOINTest refusé dans une transaction qui écrit les partitions d'un autre nœud.
37.3 Niveaux d'isolement#
MIRAJ n'a qu'un comportement, le plus strict : SERIALIZABLE. Tout se passe comme si les transactions s'exécutaient l'une après l'autre. Les quatre niveaux de la norme sont acceptés et mémorisés (les pilotes les envoient à l'ouverture d'une transaction, et les relisent), mais aucun ne relâche l'isolation : une transaction ouverte en READ COMMITTED — le niveau annoncé par défaut — lit elle aussi un instantané fixe et peut recevoir un conflit à sa validation.
| Instruction | Portée |
|---|---|
BEGIN ISOLATION LEVEL …, START TRANSACTION ISOLATION LEVEL … | la transaction ouverte |
SET TRANSACTION ISOLATION LEVEL …, SET TRANSACTION READ ONLY | READ WRITE | la transaction en cours (à écrire après BEGIN) |
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL … | les transactions suivantes de la session |
SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY | READ WRITE | toutes les instructions suivantes de la session, hors bloc comprises |
SHOW TRANSACTION ISOLATION LEVEL, SHOW transaction_isolation, current_setting('transaction_isolation') | niveau en vigueur |
SHOW transaction_isolation; -- read committed (valeur par défaut)
BEGIN ISOLATION LEVEL SERIALIZABLE;
SHOW TRANSACTION ISOLATION LEVEL; -- serializable
COMMIT;
SHOW TRANSACTION ISOLATION LEVEL; -- read committed
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SHOW transaction_isolation; -- repeatable read
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SHOW transaction_isolation; -- read uncommitted
COMMIT;
SHOW default_transaction_isolation; -- repeatable readLe niveau annoncé par défaut d'une session est read committed, comme dans PostgreSQL ; RESET ALL et SET default_transaction_isolation TO DEFAULT y reviennent. SET default_transaction_isolation TO 'serializable' et SET default_transaction_read_only TO on valent SET SESSION CHARACTERISTICS AS TRANSACTION … : ils fixent le niveau et le mode des transactions suivantes de la session (une écriture y reçoit alors 25006, hors bloc comprise ; BEGIN READ WRITE ouvre malgré tout une transaction en écriture). SHOW default_transaction_isolation et SHOW default_transaction_read_only rendent le niveau et le mode des transactions suivantes, tels que la dernière de ces instructions les a fixés, SHOW transaction_isolation et SHOW transaction_read_only le niveau et le mode en vigueur (ceux de la transaction en cours dans un bloc, ceux des transactions suivantes hors bloc). SET transaction_isolation et SET transaction_read_only valent SET TRANSACTION … : la transaction en cours seule.
SET default_transaction_read_only TO on;
UPDATE client SET ville = ville WHERE id = 1;
BEGIN READ WRITE;
UPDATE client SET ville = ville WHERE id = 1;
COMMIT;
RESET default_transaction_read_only;SET
ERROR: Cannot execute statement in a READ ONLY transaction. (25006)
BEGIN
UPDATE 1
COMMIT
RESETAvec psycopg2, connection.set_session(isolation_level='REPEATABLE READ', readonly=True) envoie ces instructions, et SHOW transaction_isolation rend ensuite repeatable read.
37.4 Modèle de concurrence#
L'isolation sérialisable est obtenue par contrôle multiversion optimiste : chaque transaction travaille sur son propre instantané des données, sans poser de verrou de ligne. Rien n'attend, sauf une ligne tenue explicitement par SELECT … FOR UPDATE (37.6). En contrepartie, une transaction peut être annulée par un conflit ; l'application la recommence (37.5).
| Situation | Comportement |
|---|---|
Un SELECT pendant qu'une autre session écrit | ne bloque jamais, ne voit jamais un état à moitié écrit, n'est jamais annulé par cette écriture |
| Deux transactions modifient la même ligne | la première garde la ligne ; la seconde reçoit 40001 tout de suite, sans attendre (sauf mise à jour différée, voir plus bas : les deux valident) |
| Une transaction écrit après avoir lu une ligne qu'une autre a modifiée et validée entre-temps | 40001 à son COMMIT |
| Deux transactions insèrent la même clé unique | la seconde reçoit 40001 tout de suite |
SELECT … FOR UPDATE, UPDATE ou DELETE d'une ligne tenue par un FOR UPDATE | attend la fin de la transaction qui la tient |
| Interblocage entre deux lectures verrouillantes | détecté aussitôt : la transaction qui fermerait le cycle reçoit 40P01, l'autre continue |
| DDL sur une table qu'une transaction ouverte a lue ou écrite | refusé tout de suite (55P03, verrou non obtenu) ; la transaction ouverte n'est pas touchée |
Les lecteurs lisent un instantané#
-- Session A -- Session B
BEGIN;
SELECT stock FROM article WHERE id = 2; -- 200
UPDATE article SET stock = stock + 5 WHERE id = 2;
SELECT stock FROM article WHERE id = 2; -- 200 : l'instantané de A ne bouge pas
COMMIT;
SELECT stock FROM article WHERE id = 2; -- 205Il en va de même d'une transaction ouverte en READ COMMITTED. Une transaction qui ne fait que lire n'est jamais annulée.
Le conflit : SQLSTATE 40001#
ERROR: could not serialize access due to concurrent update
SQLSTATE : 40001MIRAJ rend ce code (serialization_failure de PostgreSQL, classe 40 « annulation de transaction ») pour tout conflit de concurrence : écriture d'une ligne qu'une autre transaction tient, lecture périmée découverte à la validation. Un vrai interblocage (cycle d'attentes entre lectures verrouillantes, 37.6) rend 40P01 (deadlock_detected, message deadlock detected). Un pilote range les deux dans la même famille (psycopg2 : TransactionRollbackError, dont SerializationFailure et DeadlockDetected), et la réponse est la même : aucune donnée n'est perdue ni abîmée, cette transaction doit seulement être recommencée depuis son début. Un DDL (ALTER TABLE, DROP TABLE, CREATE INDEX, TRUNCATE…) refusé parce qu'une transaction ouverte tient la table n'est ni l'un ni l'autre : il rend 55P03 (lock_not_available, message could not obtain lock on relation: it is being written by an open transaction), comme un verrou que PostgreSQL n'obtiendrait pas dans le délai de lock_timeout — ici sans attendre. L'instruction se rejoue telle quelle une fois la transaction terminée.
-- Session A -- Session B
BEGIN; BEGIN;
UPDATE client SET ville = 'Lyon 3'
WHERE nom = 'Claire Dupont'; -- UPDATE 1
UPDATE client SET ville = 'Lyon 7'
WHERE nom = 'Claire Dupont';
-- ERROR 40001, immédiat
ROLLBACK;
COMMIT; -- COMMIT : 'Lyon 3'Le conflit peut aussi n'apparaître qu'à la validation, quand ce que la transaction a lu n'est plus vrai :
-- Session A -- Session B
BEGIN; BEGIN;
SELECT stock FROM article WHERE id = 2; -- 200
UPDATE article SET stock = stock + 10
WHERE code = 'CAH-96';
COMMIT;
UPDATE article SET stock = 0
WHERE code = 'CLA-A4'; -- UPDATE 1
COMMIT; -- ERROR 40001 : la lecture de A est périméeAprès un 40001 ou un 40P01, la transaction est déjà annulée en entier, et le bloc reste en échec, comme dans PostgreSQL : toute instruction reçoit 25P02 jusqu'au ROLLBACK (un COMMIT rend ROLLBACK). Un ROLLBACK TO SAVEPOINT ne la rattrape pas (25P01) : il n'y a plus de point de reprise. L'application reprend donc la transaction à BEGIN, après le ROLLBACK.
COMMIT; ERROR: could not serialize access due to concurrent update (40001)
SELECT 1; ERROR: current transaction is aborted, commands ignored until end of transaction block (25P02)
ROLLBACK; ROLLBACKHors d'un bloc, une instruction isolée qui entre en conflit est rejouée par le moteur (trois tentatives rapprochées) avant de rendre 40001 : elle n'échoue que si l'autre transaction tient encore la ligne à ce moment-là.
Mise à jour différée d'une ligne très sollicitée#
Un compteur, ou le stock d'un article vendu par toutes les caisses, est une ligne que beaucoup de transactions modifient en même temps. MIRAJ traite à part les UPDATE qui s'y prêtent : un tel UPDATE ne prend pas la ligne, il est recalculé au COMMIT sur la dernière valeur validée.
-- stock de l'article 1 : 500
-- Session A -- Session B
BEGIN; BEGIN;
UPDATE article SET stock = stock - 1
WHERE id = 1; -- UPDATE 1
UPDATE article SET stock = stock - 2
WHERE id = 1; -- UPDATE 1, pas de conflit
COMMIT; -- stock = 498
COMMIT; -- stock = 497, aucune erreurTant qu'elle n'a pas validé, la transaction qui a fait l'UPDATE relit la ligne d'après son propre instantané ; une telle lecture rend l'UPDATE dépendant de ce qu'elle a lu, et le COMMIT reçoit alors 40001 si une autre transaction a modifié la ligne entre-temps.
Un UPDATE est différé quand il vise une ligne désignée par sa clé (égalités sur toutes les colonnes de la clé primaire ou d'une contrainte d'unicité), qu'il ne modifie ni une clé, ni une clé étrangère, ni une colonne d'identité, qu'il n'a ni sous-requête, ni fonction non déterministe (random()…), ni clause RETURNING, et que les déclencheurs de la table s'y prêtent. Sinon il prend la ligne, comme dans les exemples plus haut.
Ce que cela change pour l'application :
- Erreurs au
COMMIT. Si la ligne a disparu, ou si la condition duWHEREne tient plus au moment de valider, leCOMMITrend40001. Si le recalcul lui-même échoue, leCOMMITrend l'erreur correspondante — par exemple23514quand deux ventes concurrentes feraient passer le stock sous zéro alors qu'une contrainteCHECK (stock >= 0)l'interdit (new row for relation "article" violates check constraint "stock_positif"). Dans les deux cas, toute la transaction est annulée. - Un
UPDATEqui porte sa condition (… WHERE id = 3 AND stock >= 2) n'échoue que si la condition ne tient plus auCOMMIT: c'est la forme à préférer à « lire puis écrire ». - Réglage :
SET deferred_update TO offrétablit la prise immédiate de la ligne pour la session (SHOW deferred_updaterend alors0) ;RESET deferred_updaterevient à la valeur par défaut (1, activé).
37.5 Réessayer une transaction#
Toute transaction qui écrit doit être prête à recevoir une erreur de la classe 40 et à recommencer en entier, après ROLLBACK, depuis BEGIN : pas seulement la dernière instruction. Avec psycopg2 :
import time
import psycopg2
conn = psycopg2.connect(host="127.0.0.1", port=7007, user="app", password="…", dbname="ventes")
def commander(conn, client_id, article_id, quantite, essais=5):
for essai in range(1, essais + 1):
try:
with conn: # BEGIN … COMMIT, ROLLBACK si une exception sort
with conn.cursor() as cur:
cur.execute("INSERT INTO commande (client_id) VALUES (%s) RETURNING id", (client_id,))
commande_id = cur.fetchone()[0]
cur.execute("INSERT INTO ligne_commande "
"SELECT %s, id, %s, prix FROM article WHERE id = %s",
(commande_id, quantite, article_id))
cur.execute("UPDATE article SET stock = stock - %s WHERE id = %s",
(quantite, article_id))
return commande_id
except psycopg2.errors.TransactionRollbackError: # classe 40 : 40P01, 40001
time.sleep(0.01 * essai) # courte pause, puis nouvelle tentative
raise RuntimeError("commande abandonnée après %d essais" % essais)
commander(conn, 1, 2, 5)Règles pratiques :
- tester la classe
40(les deux premiers caractères du SQLSTATE) plutôt qu'un code précis ; - reprendre aussi les lectures : les valeurs lues pendant la tentative annulée ne valent plus ;
- garder les transactions courtes, et préférer un
UPDATEqui porte sa condition à unSELECTsuivi d'unUPDATE; - pour un « lire, décider, écrire » qui ne doit pas échouer, tenir la ligne avec
SELECT … FOR UPDATE(37.6).
37.6 Lectures verrouillantes et verrous#
SELECT … FOR UPDATE#
SELECT … FOR {UPDATE | NO KEY UPDATE | SHARE | KEY SHARE} [NOWAIT | SKIP LOCKED];La requête lit les lignes et les tient jusqu'à la fin de la transaction : aucune autre transaction ne peut les écrire, ni les tenir à son tour. FOR SHARE et FOR KEY SHARE laissent les autres lecteurs FOR SHARE les tenir aussi ; FOR NO KEY UPDATE se comporte comme FOR UPDATE.
BEGIN;
SELECT stock FROM article WHERE id = 3 FOR UPDATE; -- la ligne est tenue
-- … contrôle du stock par l'application …
UPDATE article SET stock = stock - 1 WHERE id = 3;
COMMIT; -- la ligne est rendue- Une ligne déjà tenue est attendue : un autre
SELECT … FOR UPDATE, unUPDATEou unDELETEde cette ligne attend la fin de la transaction qui la tient, puis s'exécute sur la valeur validée. Deux transactions qui prennent leurs lignes dans le même ordre ne reçoivent aucune erreur. - Délai : au-delà de
lock_wait_timeoutsecondes (50 par défaut), l'instruction échoue en55P03(Lock wait timeout exceeded) ; dans un bloc, celui-ci passe en échec.SET lock_wait_timeout TO 5change ce délai pour la session ;SET lock_timeout TO '500ms'(le paramètre de PostgreSQL, en millisecondes ou avec une unité) le remplace dès qu'il est posé.SET lock_timeout TO 0signifie, comme dans PostgreSQL, attendre sans limite ;RESET lock_timeoutrend le délai delock_wait_timeout. Tant quelock_timeoutn'a pas été posé,SHOW lock_timeoutrend0mais c'estlock_wait_timeoutqui borne l'attente. NOWAIT:55P03tout de suite si une ligne est tenue.SKIP LOCKED: les lignes tenues sont écartées du résultat — le motif « file de travaux ».- Interblocage (A tient 1 et demande 2, B tient 2 et demande 1) : la transaction qui fermerait le cycle reçoit
40P01et est annulée ; l'autre obtient sa ligne. - Hors d'un bloc de transaction, la ligne n'est tenue que le temps de l'instruction.
-- Session A tient l'article 3 par FOR UPDATE. Session B :
SELECT stock FROM article WHERE id = 3 FOR UPDATE NOWAIT;
-- ERROR: Lock wait timeout exceeded; try restarting transaction (55P03)
SELECT id, stock FROM article ORDER BY id FOR UPDATE SKIP LOCKED;
-- id | stock
-- ----+-------
-- 1 | 500
-- 2 | 200
-- (2 lignes)FOR SHARE tenu par A laisse B prendre la ligne FOR KEY SHARE ou FOR SHARE, mais un FOR UPDATE ou FOR NO KEY UPDATE … NOWAIT de B rend 55P03.
LOCK TABLE#
LOCK [TABLE] nom [, …] [IN mode MODE] [NOWAIT] est accepté dans ses huit modes (ACCESS SHARE … ACCESS EXCLUSIVE). Hors d'un bloc de transaction, il est refusé comme dans PostgreSQL (25P01, LOCK TABLE can only be used in transaction blocks).
Deux modes posent un verrou, dans les deux modèles de concurrence, tenu jusqu'au COMMIT ou au ROLLBACK (XA COMMIT / XA ROLLBACK pour une branche XA, même préparée, y compris à travers un arrêt du serveur, 37.11) :
| Mode | Les autres transactions… |
|---|---|
ACCESS EXCLUSIVE (le défaut, et celui d'un TRUNCATE dans un bloc) | ne lisent ni n'écrivent plus la table |
EXCLUSIVE | la lisent encore, mais ne l'écrivent plus (SELECT … FOR UPDATE compris) |
Le verrou n'est pris qu'une fois la table libre : LOCK TABLE attend la fin des autres transactions qui y tiennent des lignes, l'ont écrite ou en tiennent le verrou — et, pour ACCESS EXCLUSIVE seulement, de celles qui l'ont simplement lue (l'ACCESS SHARE de PostgreSQL, tenu jusqu'à la fin de la transaction ; EXCLUSIVE ne l'attend pas, comme dans PostgreSQL), dans les deux modèles de concurrence. Une lecture en auto-validation ne tient rien une fois finie. Une session qui trouve la table verrouillée attend de même la fin de la transaction qui la tient. L'attente est bornée par lock_timeout (puis 55P03, lock_not_available) ; avec NOWAIT, 55P03 tout de suite ; un cycle d'attentes rend 40P01 à l'une des transactions. La transaction qui tient le verrou continue de lire et d'écrire la table normalement.
BEGIN;
LOCK TABLE article IN ACCESS EXCLUSIVE MODE; -- les autres sessions attendent
UPDATE article SET prix = prix * 1.02;
COMMIT; -- verrou renduLes autres modes (ACCESS SHARE … SHARE ROW EXCLUSIVE) ne posent aucun verrou : l'isolation sérialisable de MIRAJ les rend inutiles. Une vue ou une table temporaire n'est pas verrouillée.
Verrous consultatifs#
Des verrous que l'application nomme elle-même, pour sérialiser un traitement (une clôture, un import) sans verrouiller de données.
| Fonction | Effet |
|---|---|
pg_advisory_lock(clé) | prend le verrou pour la session, en attendant s'il est tenu par une autre session |
pg_try_advisory_lock(clé) | le prend pour la session s'il est libre : t, sinon f, sans attendre |
pg_advisory_unlock(clé) | rend un verrou de session : t ; f avec l'avertissement you don't own a lock of type ExclusiveLock si la session ne le tenait pas |
pg_advisory_xact_lock(clé) | prend le verrou pour la transaction, en attendant s'il est tenu |
pg_try_advisory_xact_lock(clé) | le prend pour la transaction s'il est libre : t, sinon f |
La clé est un bigint, ou deux integer (pg_try_advisory_lock(1, 2)). Un même verrou ne peut être tenu que par une session à la fois, qu'il ait été pris pour la session ou pour la transaction.
- Les verrous de session sont cumulables (pris deux fois, ils se rendent deux fois) : ni
COMMITniROLLBACKne les rendent, seulspg_advisory_unlocket la fermeture de la session. - Les verrous de transaction sont rendus d'eux-mêmes à la fin de la transaction, validée ou annulée ; hors d'un bloc, à la fin de l'instruction.
SELECT pg_try_advisory_lock(42); -- t
-- … traitement exclusif …
SELECT pg_advisory_unlock(42); -- t
BEGIN;
SELECT pg_advisory_xact_lock(7); -- une autre session : pg_try_advisory_lock(7) rend f
-- … traitement …
COMMIT; -- verrou 7 rendu37.7 Paramètres de session : SET, SHOW, RESET#
SET [SESSION | LOCAL] nom {TO | =} {valeur | 'valeur' | DEFAULT};
SHOW nom;
SHOW ALL;
RESET nom;
RESET ALL;SETchange un paramètre pour la session. Fait dans un bloc de transaction, il est défait si la transaction est annulée (ROLLBACK,COMMITen échec) et par unROLLBACK TO SAVEPOINTvers un point posé avant lui. Une liste (SET nom TO a, b) est une seule valeur, le textea, b.SET LOCALne vaut que jusqu'à la fin de la transaction en cours, validée ou annulée ; unSETde session du même paramètre fait après lui l'emporte. Hors d'un bloc de transaction, il est sans effet et rend l'avertissementSET LOCAL can only be used in transaction blocks(25P01).- Un nom qui n'est pas un paramètre de PostgreSQL (ni une variable de MIRAJ) est refusé (
42704,unrecognized configuration parameter "nom"), parSETcomme parRESET(SHOW nom:42704,Unknown system variable 'nom'). Tous les paramètres de configuration de PostgreSQL 16 et 17 sont reconnus, sans tenir compte de la casse : ceux qui sont sans objet ici sont mémorisés sans effet (tableau ci-dessous), si bien que lesSETqu'émettentpg_dump,pg_restore,psqlet les pilotes passent. - Un nom à point (
SET monappli.locataire TO '42') est un paramètre personnalisé : mémorisé pour la session, relu parSHOW monappli.locataireetcurrent_setting('monappli.locataire')(42704tant qu'il n'a pas été posé), et soumis aux mêmes règles de transaction. SHOW nomrend une ligne et une colonne, nommée comme le paramètre. Un nom inconnu donne42704.SHOW ALLrend tous les paramètres (name,setting,description), comme la tablepg_catalog.pg_settings; les variables de MIRAJ et les paramètres personnalisés posés y figurent aussi.RESET nometSET nom TO DEFAULTrendent au paramètre sa valeur du début de la session : celle desoptionsde la connexion si elles le posent (40.5), sinon sa valeur par défaut. Cela vaut pour les paramètres de PostgreSQL comme pour les variables de MIRAJ (lock_wait_timeout,statement_timeout…) ; une variable de MIRAJ que lesoptionsne posent pas reprend la valeur courante du serveur (unSET GLOBALfait depuis le début de la session est repris).RESET ALLrend à tous les paramètres leur valeur du début de la session, schéma courant (search_path) compris (37.8).current_setting('nom')lit un paramètre dans une expression (42704s'il est inconnu, NULL aveccurrent_setting('nom', true)).set_config('nom', 'valeur', is_local)pose un paramètre depuis une expression et rend la valeur : c'est unSET(is_localfaux) ou unSET LOCAL(is_localvrai), avec les mêmes contrôles et les mêmes règles de transaction. Voir «set_config» ci-dessous.
Les paramètres sont de deux sortes : ceux de PostgreSQL, que les clients et pilotes envoient et relisent, et les variables de MIRAJ, qui règlent le moteur. Les uns comme les autres se lisent par SHOW.
Paramètres de PostgreSQL#
| Paramètre | Valeur, effet |
|---|---|
TimeZone | fuseau de la session, voir 37.9 |
statement_timeout | durée maximale d'une instruction, voir 37.9 |
search_path | schéma courant, voir 37.8 |
transaction_isolation, transaction_read_only | voir 37.3 ; lus par SHOW |
application_name | texte libre, donné à la connexion ou par SET ; SHOW application_name, current_setting('application_name'), pg_settings et, pour chaque session, pg_stat_activity.application_name le rendent |
client_encoding | toujours UTF8 ; une autre valeur est refusée (0A000) ; SET NAMES 'UTF8' est accepté |
DateStyle | toujours ISO, MDY ; un autre style de sortie est refusé (0A000) |
standard_conforming_strings | on |
extra_float_digits, IntervalStyle | acceptés ; SHOW rend 1 et postgres |
server_version, server_version_num | 16.4, 160004 : la version de PostgreSQL annoncée aux clients |
is_superuser, session_authorization | on pour un compte qui a tous les privilèges sur *.* (rôles actifs compris), nom du compte sous lequel la session agit ; ils suivent SET SESSION AUTHORIZATION et SET ROLE (38.4), et sont renvoyés au client quand ils changent |
lock_timeout | attente maximale d'une ligne tenue (55P03 au-delà), en millisecondes ou avec une unité ('2s') ; 0 : sans limite ; jamais posé : celle de lock_wait_timeout (37.6) |
session_replication_role | origin (défaut), local ou replica. replica suspend pour la session les déclencheurs et les clés étrangères (contrôles et actions ON DELETE / ON UPDATE), comme pour l'application d'un flux de réplication ou le chargement d'un vidage ; SHOW foreign_key_checks rend alors 0. Réservé aux comptes qui ont SUPER ou SYSTEM_VARIABLES_ADMIN (42501 sinon). Les lignes chargées ainsi ne sont pas contrôlées au retour à origin |
idle_in_transaction_session_timeout | une session restée sans rien envoyer dans un bloc de transaction (ouvert ou en échec) plus longtemps que ce délai est fermée par le serveur (FATAL, 25P03, terminating connection due to idle-in-transaction timeout) et sa transaction annulée ; 0 : aucun délai. Appliqué par le serveur réseau (sans objet en mode embarqué) |
default_transaction_isolation, default_transaction_read_only | niveau et mode des transactions suivantes de la session, voir 37.3 |
work_mem, synchronous_commit, enable_seqscan, jit, client_min_messages, row_security, check_function_bodies, xmloption, default_tablespace, default_table_access_method, transaction_timeout, log_statement, geqo*, *_cost, max_parallel_*… | tous les autres paramètres de session de PostgreSQL : acceptés et mémorisés sans effet, SHOW, current_setting et pg_settings rendent la valeur posée (vide, ou la valeur usuelle de PostgreSQL pour les plus courants, tant qu'ils ne l'ont pas été) |
server_version, server_encoding, integer_datetimes, is_superuser, block_size, wal_block_size… | en lecture seule : SET rend 55P02 (parameter "nom" cannot be changed) |
shared_buffers, max_connections, wal_level, port, listen_addresses… | réglés au démarrage du serveur PostgreSQL : SET, SET LOCAL, RESET et set_config rendent 55P02 (parameter "nom" cannot be changed without restarting the server) ; dans les options de la connexion, ils la refusent. SHOW les lit (vide) |
fsync, autovacuum, ssl, checkpoint_timeout, log_destination, archive_command… | réglés dans le fichier de configuration de PostgreSQL : 55P02 de même (parameter "nom" cannot be changed now) |
log_connections, post_auth_delay… | réglés à l'ouverture de la connexion : SET rend 55P02 (parameter "nom" cannot be set after connection start) |
Variables de MIRAJ utiles en session#
| Variable | Défaut | Rôle |
|---|---|---|
autocommit | 1 | voir 37.1 |
lock_wait_timeout | 50 | attente maximale d'une ligne tenue, en secondes (37.6) |
deferred_update | 1 | mise à jour différée (37.4) |
sql_mode | celui du serveur | l'élément POSTGRESQL ne peut pas être retiré par SET : le langage est fixé au démarrage |
SHOW VARIABLES LIKE 'lock%', @@lock_wait_timeout et les variables utilisateur (SET @v = 5, SELECT @v) restent disponibles comme extensions de MIRAJ.
SET statement_timeout TO '5s';
BEGIN;
SET LOCAL statement_timeout TO '30s'; -- pour cette transaction seulement
SET monappli.locataire TO '42';
SAVEPOINT s;
SET lock_timeout TO '2s';
ROLLBACK TO SAVEPOINT s; -- lock_timeout revient à 0
COMMIT;
SHOW statement_timeout; -- 5s
SHOW monappli.locataire; -- 42set_config#
SELECT set_config('monappli.locataire', '42', false); -- comme SET : pour la session
BEGIN;
SELECT set_config('monappli.locataire', '7', true); -- comme SET LOCAL : pour la transaction
SELECT current_setting('monappli.locataire'); -- 7
COMMIT;
SELECT current_setting('monappli.locataire'); -- 42- Le nom est celui d'un paramètre de PostgreSQL, d'une variable de MIRAJ ou d'un paramètre personnalisé ;
search_path,TimeZone,statement_timeoutetrolese posent ainsi comme par leurSET. Un nom inconnu rend42704, un nom mal formé42602, un paramètre du serveur55P02, une valeur refusée22023ou0A000. - Une valeur NULL rend au paramètre sa valeur du début de la session (comme
RESET) ; la fonction rend alors NULL. is_localvrai hors d'un bloc de transaction : la valeur ne vaut que pour l'instruction, sans avertissement ; rien n'en reste ensuite.- La valeur passe en paramètre lié (
SELECT set_config('monappli.locataire', $1, true)) et la fonction s'emploie dans toute instruction, y compris dans le corps d'une fonction ou d'une procédure PL/pgSQL (PERFORM set_config(…)), dont les instructions suivantes lisent la valeur parcurrent_setting. - Le paramètre est appliqué à la fin de l'instruction qui appelle
set_config, si elle réussit (une instruction en échec ne pose rien).
Différences avec PostgreSQL#
set_configapplique sa valeur à la fin de l'instruction et non au moment de l'appel : dans la même instruction SQL,current_settingpeut encore rendre l'ancienne valeur (SELECT set_config('a.b', 'x', false), current_setting('a.b')) ; lisez-la par l'instruction suivante. Une valeur invalide (22023) est signalée à ce moment-là : en auto-validation, les écritures de l'instruction sont alors déjà validées.session_replication_role = replicasuspend tous les déclencheurs de l'utilisateur : il n'y a pas de déclencheurENABLE REPLICAouENABLE ALWAYSqui s'exécuterait quand même. L'historique d'une table versionnée continue d'être tenu.- Une transaction XA (37.11) défait ses
SETàXA ROLLBACKet met fin à sesSET LOCALà sa fin, y compris aprèsXA PREPARE— là oùPREPARE TRANSACTIONde PostgreSQL garde lesSETde la transaction comme s'ils étaient validés.
37.8 Schéma courant et search_path#
Chaque base de MIRAJ est un schéma PostgreSQL, et il n'y a qu'une base PostgreSQL, miraj. La session a un schéma courant : celui où une table nommée sans schéma est cherchée et créée. Il est fixé à la connexion par le paramètre dbname (quand il nomme une base de MIRAJ), puis par :
SET search_path TO ventes;
SET search_path TO ventes, public;SET search_path fait du premier schéma nommé qui existe (et que le compte peut lire) le schéma courant. Il n'y a pas de chemin à plusieurs étages : les schémas suivants de la liste ne sont pas consultés.
SET search_path TO ventes, archives;
SELECT count(*) FROM client; -- ventes.client, jamais archives.client
SELECT count(*) FROM archives.client; -- une table d'un autre schéma se nomme en entier
SHOW search_path; -- ventes| Écriture | Effet |
|---|---|
SET search_path TO a, b | schéma courant : le premier de a, b qui existe |
SET search_path TO inexistant | accepté ; le schéma courant ne change pas |
SET search_path TO public, TO "$user", public | accepté ; public désigne le schéma courant, qui ne change pas |
SET search_path TO '' | accepté ; le schéma courant ne change pas (pg_dump l'écrit set_config('search_path', '', false)) |
RESET search_path, SET search_path TO DEFAULT, RESET ALL, DISCARD ALL | schéma courant du début de la session : celui de la connexion (dbname, search_path du message de démarrage) ou du dernier USE ; aucun si la session s'est ouverte sans base. Gardé tel qu'il est si ce schéma a été supprimé depuis |
SHOW search_path | le schéma courant, seul |
current_schema, current_schema() | le schéma courant |
current_schemas(true), current_schemas(false) | {pg_catalog,ventes}, {ventes} |
current_database() | miraj |
USE ventes | extension de MIRAJ : même effet que SET search_path TO ventes |
pg_catalog passe avant tout schéma, comme dans PostgreSQL : SELECT * FROM pg_class lit la table du catalogue (40. Catalogue et outils). Un nom qualifié par public. désigne le schéma courant.
37.9 Fuseau horaire et délai d'instruction#
Fuseau de la session#
SET TIME ZONE 'Europe/Paris';
SET TimeZone TO 'UTC';
SET TIME ZONE -5; -- décalage en heures
SET TIME ZONE LOCAL; -- fuseau de la machine du serveur
SET TIME ZONE DEFAULT; -- valeur par défaut du serveur
SHOW TIME ZONE; -- ou SHOW timezoneLe fuseau de la session règle now(), la lecture et l'affichage des timestamptz, AT TIME ZONE et current_setting('TimeZone') (32. Types de données, 36. Fonctions SQL). Un fuseau inconnu est refusé :
ERROR: invalid value for parameter "TimeZone": "Mars/Olympus" (22023)Le fuseau par défaut est celui du serveur ; un client peut en demander un autre dès l'ouverture de la connexion par ses options (options=-c TimeZone=Europe/Paris, voir 40.5), ou par le paramètre TimeZone du message de démarrage, que pgjdbc et Npgsql envoient d'eux-mêmes : la session prend alors le fuseau du client. Un fuseau inconnu du serveur y est ignoré. Un décalage écrit en texte (SET TIME ZONE '+02:00') se lit à l'est de Greenwich (l'heure de la session est alors en avance de deux heures sur UTC), à l'inverse de la notation POSIX de PostgreSQL : préférez un nom de fuseau. Après SET TIME ZONE LOCAL, SHOW TimeZone rend le nom du fuseau de la machine du serveur (par exemple Etc/GMT-1).
Durée maximale d'une instruction#
SET statement_timeout TO 10000; -- millisecondes : SHOW rend 10s
SET statement_timeout TO 1500; -- SHOW rend 1500ms
SET statement_timeout TO '2s'; -- ou '1min'
SHOW statement_timeout; -- 2s
SELECT pg_sleep(3);
RESET statement_timeout; -- valeur du serveurERROR: Query was interrupted: execution time limit 2.0 sec exceeded (57014)RESET statement_timeout rend la durée maximale réglée sur le serveur (max_statement_time, 300 secondes sans autre réglage : SHOW statement_timeout rend alors 5min) ; SET statement_timeout TO 0 supprime toute limite pour la session.
Une instruction annulée par le client (Ctrl-C de psql, connection.cancel() de psycopg2) ou par KILL QUERY reçoit le même SQLSTATE 57014. Annulée pendant une attente (ligne tenue par une autre transaction, table occupée par l'instruction longue d'une autre session), elle n'écrit rien ; un COMMIT annulé avant d'avoir commencé ne valide rien et laisse le bloc en échec.
Heure d'une transaction#
Comme dans PostgreSQL, now(), current_timestamp, transaction_timestamp(), current_date, current_time, localtimestamp et les colonnes DEFAULT now() rendent, dans un bloc de transaction, l'heure du début de la transaction (son BEGIN) : toutes les lignes qu'elle écrit portent le même instant. statement_timestamp() rend l'heure du début de l'instruction et clock_timestamp() l'heure de l'horloge à chaque appel. Hors d'un bloc, chaque instruction est sa propre transaction : now() et statement_timestamp() y sont égaux.
BEGIN;
SELECT now() = statement_timestamp();
SELECT pg_sleep(1);
SELECT now() = statement_timestamp(), now() = transaction_timestamp(), now() < clock_timestamp();
COMMIT;BEGIN
now() = statement_timestamp()
-------------------------------
t
(1 ligne)
pg_sleep(1)
-------------
0
(1 ligne)
now() = statement_timestamp() | now() = transaction_timestamp() | now() < clock_timestamp()
-------------------------------+---------------------------------+---------------------------
f | t | t
(1 ligne)
COMMIT37.10 Curseurs, instructions préparées, remise à zéro#
Curseurs SQL#
DECLARE nom [SCROLL | NO SCROLL] CURSOR [WITH HOLD | WITHOUT HOLD] FOR requête;
FETCH [sens] [FROM | IN] nom;
MOVE [sens] [FROM | IN] nom;
CLOSE {nom | ALL};
sens : NEXT | PRIOR | FIRST | LAST | ABSOLUTE n | RELATIVE n | n | ALL
| FORWARD [n | ALL] | BACKWARD [n | ALL]Sans sens, FETCH nom lit la ligne suivante (NEXT). Les nombres sont signés (FETCH -1 recule d'une ligne, ABSOLUTE -1 est la dernière) ; 0 (FETCH 0, RELATIVE 0) relit la ligne courante.
Un curseur lit le résultat d'une requête par morceaux ; c'est ce qu'emploient les « curseurs nommés » de psycopg2 (conn.cursor(name='lecture')). Le résultat entier est calculé à DECLARE et gardé par le serveur ; la lecture se fait dans les deux sens, avec les mêmes positions que PostgreSQL (au-delà de la dernière ligne, FETCH PRIOR rend la dernière ; MOVE rend le nombre de lignes que FETCH aurait rendues).
BEGIN;
DECLARE c_art CURSOR FOR SELECT id, code FROM article ORDER BY id;
FETCH 2 FROM c_art;
FETCH NEXT FROM c_art;
FETCH ALL FROM c_art;
CLOSE c_art;
COMMIT;BEGIN
DECLARE CURSOR
id | code
----+--------
1 | STY-01
2 | CAH-96
(2 lignes)
FETCH 2
id | code
----+--------
3 | CLA-A4
(1 ligne)
FETCH 1
id | code
----+------
(0 lignes)
FETCH 0
CLOSE CURSOR
COMMIT- Sans
WITH HOLD, un curseur exige un bloc de transaction (25P01,DECLARE CURSOR can only be used in transaction blocks) et disparaît à sa fin. WITH HOLDse déclare hors transaction et survit auxCOMMIT; fermez-le parCLOSE.CLOSE ALLferme tous les curseurs de la session.- Un curseur fermé ou inconnu :
34000(cursor "c5" does not exist) ; dans un bloc, l'erreur le met en échec comme toute autre. SCROLLetNO SCROLLensemble :42P11(cannot specify both SCROLL and NO SCROLL).- Un curseur
NO SCROLLrefuse tout recul,FETCH 0et le retour au début compris :55000(cursor can only scan forward). SansSCROLLniNO SCROLL, le curseur se lit dans les deux sens (PostgreSQL en décide selon le plan de la requête : déclarezSCROLLpour un programme qui doit fonctionner sur les deux).
BEGIN;
DECLARE c_dos SCROLL CURSOR FOR SELECT id FROM article ORDER BY id;
FETCH LAST FROM c_dos; -- 3
FETCH BACKWARD 2 FROM c_dos; -- 2, puis 1
FETCH ABSOLUTE 2 FROM c_dos; -- 2
MOVE BACKWARD ALL FROM c_dos; -- MOVE 1
COMMIT;Instructions préparées#
PREPARE nom [(types)] AS instruction; -- paramètres $1, $2…
EXECUTE nom [(valeurs)];
DEALLOCATE [PREPARE] {nom | ALL};PREPARE art_par_prix (numeric) AS
SELECT code, prix FROM article WHERE prix >= $1 ORDER BY prix;
EXECUTE art_par_prix(2);
DEALLOCATE art_par_prix;
PREPARE maj_stock (int, int) AS UPDATE article SET stock = stock + $2 WHERE id = $1;
EXECUTE maj_stock(1, 0);
DEALLOCATE ALL;PREPARE
code | prix
--------+------
CAH-96 | 2.50
CLA-A4 | 4.90
(2 lignes)
SELECT 2
DEALLOCATE
PREPARE
UPDATE 1
DEALLOCATE ALLLa liste de types est acceptée sans contraindre les paramètres. EXECUTE rend l'étiquette de l'instruction préparée (SELECT 2, UPDATE 1…). Un nom inconnu donne 26000 (Unknown prepared statement handler (art_par_prix) given to EXECUTE). Préparer deux fois le même nom est refusé (42P05, prepared statement "q" already exists) : libérer d'abord l'instruction par DEALLOCATE. Les pilotes préparent d'ordinaire par le protocole (Parse, Bind) plutôt que par ces instructions (31. Prise en main).
DISCARD#
| Instruction | Effet |
|---|---|
DISCARD ALL | libère les instructions préparées, les curseurs, les tables temporaires et les abonnements LISTEN de la session, rend aux paramètres leur valeur du début de la session (comme RESET ALL) et rend le compte de la connexion après SET SESSION AUTHORIZATION (38.4) ; refusé dans un bloc de transaction (25001, DISCARD ALL cannot run inside a transaction block) |
DISCARD TEMP, DISCARD PLANS, DISCARD SEQUENCES | acceptés |
Un groupe de connexions peut donc recycler une session par DISCARD ALL, comme avec PostgreSQL : le schéma courant redevient lui aussi celui du début de la session (37.8). Une application qui choisit son schéma par SET search_path après la connexion le repose donc après chaque DISCARD ALL, comme avec PostgreSQL ; donnez-le plutôt à la connexion (dbname, ou search_path dans les paramètres de démarrage).
Sessions en cours#
pg_catalog.pg_stat_activity liste les sessions (pid, usename, application_name, state, query) ; pg_backend_pid() rend le numéro de la sienne. application_name y est celui que la session a donné à la connexion ou par SET, relevé à la fin de sa dernière commande. Pour interrompre une session, les fonctions de PostgreSQL et l'instruction KILL de MIRAJ ont le même effet :
SELECT pid, usename, state, query FROM pg_stat_activity ORDER BY pid;
SELECT pg_cancel_backend(14); -- interrompt l'instruction en cours de la session 14 : t
KILL QUERY 14; -- de même
SELECT pg_terminate_backend(14); -- ferme la session 14 : t
KILL 14; -- de même
-- toutes les sessions au repos d'une application, sauf la sienne
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE pid <> pg_backend_pid() AND application_name = 'rapport' AND state = 'idle';pg_cancel_backend(pid) et pg_terminate_backend(pid) rendent t dès la demande partie, sans attendre son effet, et NULL pour un pid NULL. Un pid inconnu donne f avec l'avertissement PID 99 is not a PostgreSQL backend process (KILL répond 42704). La demande part à l'appel, ligne par ligne : elle n'est pas reprise si l'instruction échoue ensuite, ni par un ROLLBACK. pg_terminate_backend(pid, délai) attend au plus délai millisecondes que la session ait quitté le serveur, et rend f avec un avertissement si elle y est encore. Les droits sont ceux de KILL : un compte atteint ses propres sessions, toutes avec le privilège PROCESS (ou SUPER, CONNECTION_ADMIN) ; sinon 42501, permission denied to terminate process (ou to cancel query). Une session qui se vise elle-même reçoit aussitôt 57014 (pg_cancel_backend) ou l'erreur fatale 57P01 (pg_terminate_backend).
La session fermée par pg_terminate_backend ou KILL (ou KILL CONNECTION) reçoit, avant la fin de sa connexion, la même erreur fatale que de PostgreSQL : FATAL, 57P01, terminating connection due to administrator command. Elle la reçoit dans tous les états : en attente d'une commande, pendant une requête (abandonnée ; l'erreur fatale remplace celle de l'instruction), dans un bloc de transaction (annulé, ses verrous relâchés au plus tard à la fin du flux), pendant un COPY, pendant l'attente d'un verrou (l'instruction en attente n'écrit rien, rien n'est validé). Le serveur envoie la même erreur à toutes les connexions quand il s'arrête par SHUTDOWN. Une session restée sans rien envoyer plus longtemps que le délai d'inactivité du serveur (option --idle-timeout, 8 heures par défaut) reçoit FATAL, 57P05, terminating connection due to idle-session timeout. Le message n'est pas attendu par un client qui ne lit plus ce que le serveur lui envoie : sa connexion est fermée sans lui.
37.11 Transactions XA (validation en deux phases)#
La validation en deux phases s'écrit avec les instructions XA de MIRAJ, disponibles en langage PostgreSQL (les formes PREPARE TRANSACTION, COMMIT PREPARED de PostgreSQL ne le sont pas) :
XA START 'cmd-42';
UPDATE article SET stock = stock - 1 WHERE id = 1;
XA END 'cmd-42';
XA PREPARE 'cmd-42';
XA RECOVER; -- liste les branches préparées
XA COMMIT 'cmd-42'; -- ou XA ROLLBACK 'cmd-42'Une branche préparée survit à la fermeture de sa session et à l'arrêt du serveur, propre ou brutal, jusqu'à ce qu'un coordinateur la valide ou l'annule — avec ses verrous de table (LOCK TABLE … IN [ACCESS] EXCLUSIVE MODE, TRUNCATE dans la branche), comme une transaction préparée de PostgreSQL : après un redémarrage, les autres sessions attendent toujours sa décision (lock_timeout, puis 55P03) ; une table qu'elle a vidée par TRUNCATE puis écrite est reprise vidée, avec les seules lignes écrites depuis. Une branche qui ne tient qu'un verrou de table, sans rien écrire, survit elle aussi. Les SET faits dans la branche suivent son sort comme ceux d'un bloc BEGIN : défaits par XA ROLLBACK, gardés par XA COMMIT, et les SET LOCAL finissent avec elle. La session qui tranche une branche préparée par une autre session (fermée depuis) garde ses propres paramètres. Les états, les règles et les limites sont décrits au chapitre 13.
37.12 Non pris en charge et écarts#
| Forme | Réponse ou comportement |
|---|---|
PREPARE TRANSACTION 'x', COMMIT PREPARED, ROLLBACK PREPARED | 42601 (erreur de syntaxe) ; employez XA (37.11) |
BEGIN ISOLATION LEVEL SNAPSHOT, SET TRANSACTION SNAPSHOT '…' | 42601 |
pg_advisory_lock_shared, pg_advisory_unlock_all | 42883 (FUNCTION … does not exist) |
pg_cursors, pg_prepared_statements | 42P01 : ces vues n'existent pas |
CHECKPOINT | 42601 |
SET client_encoding TO 'LATIN1' | 0A000 (the server only speaks UTF8) |
SET DateStyle TO 'SQL, DMY' | 0A000 (only the ISO output style) |
KILL QUERY d'une session inconnue | 42704 (Unknown thread id) |
Écarts de comportement, détaillés dans ce chapitre :
- les niveaux d'isolement ne relâchent rien : tous lisent un instantané fixe (37.3) ;
- un conflit (
40001) ou un interblocage (40P01) annule toute la transaction ;ROLLBACK TO SAVEPOINTne la rattrape pas (37.4) ; - un écrivain ne s'attend pas derrière un autre : il échoue tout de suite, sauf pour une ligne tenue par
FOR UPDATE(37.4, 37.6) ;NOWAITet un délai dépassé rendent55P03; - seules certaines instructions de définition s'annulent avec la transaction ; les autres la valident d'abord (37.1) ;
COMMIT,ROLLBACKhors transaction etBEGINrépété ne donnent pas d'avertissement ;LOCK TABLEne pose de verrou qu'enEXCLUSIVEetACCESS EXCLUSIVE(37.6) ;set_configappliqué à la fin de l'instruction,SETdans une routine (37.7) ;- un DDL refusé par une transaction ouverte rend
55P03tout de suite, sans attendre (37.4) ; TRUNCATEdans un bloc : annulable et vidé en bloc, table disque et table partitionnée de l'édition Cluster comprises ; seule une table partitionnée dont une partition est tenue par un autre nœud voit ses lignes supprimées une à une, plus lentement (34.6) ;search_pathne retient qu'un schéma (37.8) ;txid_current()rend un numéro positif, le même dans toute la transaction, mais propre à MIRAJ : il n'est ni croissant d'une transaction à l'autre, ni comparable à ceux de PostgreSQL.
La liste condensée de la grammaire est au chapitre 29.
Voir aussi#
- 34. Langage DML pour
INSERT,UPDATE,DELETE,RETURNINGetON CONFLICT. - 38. Comptes et privilèges pour ce qu'une session a le droit de faire.
- 39. Routines et déclencheurs pour les transactions vues d'une fonction.
- 13. Transactions et concurrence, pour le même modèle décrit en langage MIRAJ.