Les erreurs dans PostgreSQL peuvent arriver pour plein de raisons : violation de contraintes (NOT NULL, UNIQUE, CHECK), erreurs de syntaxe, valeurs dupliquées, etc. Si tu ne les interceptes pas et ne les gères pas, toute la transaction externe peut être rollbackée complètement. Pour des opérations business fiables, c'est super important de bien les gérer.
PL/pgSQL te file un mécanisme puissant avec les blocs BEGIN ... EXCEPTION ... END pour intercepter et gérer les erreurs dans les fonctions et procédures. C'est un peu comme try-catch en Python ou Java, mais avec des particularités liées aux transactions PostgreSQL.
Point important :
Chaque bloc BEGIN ... EXCEPTION ... END agit comme un “savepoint virtuel”. Si une exception se produit, tous les changements dans ce bloc sont automatiquement annulés. C'est la seule façon correcte de faire un rollback partiel dans les fonctions et procédures PL/pgSQL.
Syntaxe de gestion des erreurs avec EXCEPTION
BEGIN
-- code principal
EXCEPTION
WHEN TYPE_ERREUR THEN
-- gestion d'une erreur spécifique
WHEN AUTRE_TYPE_ERREUR THEN
-- autre gestionnaire
WHEN OTHERS THEN
-- gestion de toutes les autres erreurs
END;
Illustration dans les fonctions/procédures
DO $$
BEGIN
RAISE NOTICE 'Une erreur va arriver...';
PERFORM 1 / 0;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Division par zéro interceptée !';
END;
$$;
Exemple : update avec gestion et rollback des erreurs
Imaginons qu'on a une table de commandes :
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount NUMERIC NOT NULL,
status TEXT NOT NULL
);
On va créer une fonction qui met à jour le statut d'une commande et, en cas d'erreur, ne change rien :
CREATE OR REPLACE FUNCTION update_order_status(order_id INT, new_status TEXT)
RETURNS VOID AS $$
BEGIN
BEGIN
UPDATE orders
SET status = new_status
WHERE id = order_id;
-- Simulation d'une erreur
IF new_status = 'FAIL' THEN
RAISE EXCEPTION 'On simule une erreur !';
END IF;
RAISE NOTICE 'Statut de la commande % mis à jour', order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erreur lors de la mise à jour de la commande % : %', order_id, SQLERRM;
-- Tous les changements dans ce bloc seront annulés automatiquement !
-- On relance l'erreur pour gestion externe si besoin
RAISE;
END;
END;
$$ LANGUAGE plpgsql;
Comment ça marche dans les procédures avec gestion explicite des transactions
Dans les procédures (CREATE PROCEDURE), tu peux utiliser COMMIT, ROLLBACK, SAVEPOINT, mais tu ne peux pas faire ROLLBACK TO SAVEPOINT. Si tu veux rollback juste une partie des opérations dans la procédure, utilise toujours BEGIN ... EXCEPTION ... END :
CREATE OR REPLACE PROCEDURE pay_order(order_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
-- Toute la procédure peut utiliser COMMIT/ROLLBACK, mais pour rollback une étape précise :
BEGIN
UPDATE accounts
SET balance = balance - amount
WHERE id = (SELECT account_id FROM orders WHERE id = order_id);
-- erreur
IF amount < 0 THEN
RAISE EXCEPTION 'Le montant ne peut pas être négatif !';
END IF;
UPDATE orders SET status = 'PAID' WHERE id = order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erreur lors du traitement du paiement de la commande % : %', order_id, SQLERRM;
-- les changements dans ce bloc seront annulés automatiquement
END;
COMMIT; -- Tu peux finir explicitement la transaction seulement dans les procédures !
END;
$$;
Logging des erreurs
C'est important non seulement d'intercepter les erreurs, mais aussi de les enregistrer pour analyse plus tard.
CREATE TABLE error_log (
id SERIAL PRIMARY KEY,
order_id INT,
error_message TEXT,
error_time TIMESTAMP DEFAULT now()
);
Dans une fonction ou une procédure :
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log (order_id, error_message)
VALUES (order_id, SQLERRM);
RAISE NOTICE 'Erreur enregistrée dans le log : %', SQLERRM;
RAISE;
Limitations et subtilités importantes dans PostgreSQL 17
Dans les fonctions (CREATE FUNCTION ... ), tu ne peux pas utiliser les commandes de gestion de transaction (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT) ! Toutes les fonctions s'exécutent entièrement dans la transaction externe.
Dans les procédures (CREATE PROCEDURE ... ), tu peux explicitement écrire SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK. MAIS : ROLLBACK TO SAVEPOINT — INTERDIT ! (Tu auras une erreur de syntaxe si tu essaies d'utiliser ROLLBACK TO SAVEPOINT dans une procédure PL/pgSQL).
Le rollback "d'une partie du code" dans les fonctions et procédures se fait avec les blocs BEGIN ... EXCEPTION ... END. Si une erreur arrive, tout dans le bloc est annulé automatiquement, et l'exécution peut continuer.
Les procédures (CREATE PROCEDURE) ne peuvent pas être lancées dans une fonction ou via SELECT — seulement avec la commande CALL ... à part.
Comment vraiment faire un "rollback partiel" en PL/pgSQL ?
La seule façon qui marche — c'est de gérer les erreurs avec les blocs BEGIN ... EXCEPTION ... END. Ce bloc crée automatiquement un point de sauvegarde, et en cas d'erreur, il annule les changements à l'intérieur, sans toucher au reste de la procédure/fonction.
Exemple avec EXCEPTION (méthode recommandée) :
CREATE OR REPLACE PROCEDURE demo_savepoint()
LANGUAGE plpgsql
AS $$
BEGIN
-- Un peu de code
BEGIN
-- Ici, une erreur ne va pas rollback toute la procédure,
-- juste ce bloc !
INSERT INTO demo VALUES ('mauvaises données'); -- peut-être va causer une erreur
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erreur gérée, changements dans le bloc annulés';
END;
-- Ici, l'exécution continue !
END;
$$;
Exemple : chargement d'un batch de données avec protection contre le rollback total
CREATE OR REPLACE PROCEDURE load_big_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM import_table LOOP
BEGIN
INSERT INTO target_table (col1, col2)
VALUES (rec.col1, rec.col2);
EXCEPTION WHEN OTHERS THEN
INSERT INTO import_errors (err_msg)
VALUES ('Erreur dans la ligne : ' || rec.col1 || ' : ' || SQLERRM);
-- changements dans ce bloc annulés !
END;
END LOOP;
COMMIT; -- ok seulement si la procédure est lancée hors d'une transaction externe explicite !
END;
$$;
-- Appel de la procédure
CALL load_big_batch();
À noter : si tu appelles cette procédure depuis un client qui a déjà démarré une transaction (genre Python avec autocommit=False), un COMMIT ou SAVEPOINT dans la procédure va causer une erreur.
Conseils pour gérer les savepoints imbriqués et EXCEPTION
- N'utilise pas ROLLBACK TO SAVEPOINT dans PL/pgSQL ! Ça va causer une erreur de syntaxe.
- Pour rollback partiel, utilise toujours des blocs imbriqués BEGIN ... EXCEPTION ... END.
- N'oublie pas que COMMIT et ROLLBACK dans les procédures redémarrent la transaction — tu peux les utiliser seulement si la procédure tourne en mode autocommit !
- Loggue les erreurs dans une table à part pour ne pas perdre d'infos sur les lignes incorrectes.
- Si une opération business doit être strictement atomique (tout ou rien) — fais-en une fonction, sans COMMIT/ROLLBACK dedans ; si tu veux gérer étape par étape — fais-en une procédure.
Exemple : import par batch avec gestion partielle
CREATE OR REPLACE PROCEDURE import_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM staging_table LOOP
BEGIN
INSERT INTO data_table (data)
VALUES (rec.data);
EXCEPTION
WHEN unique_violation THEN
INSERT INTO import_log (msg)
VALUES ('Doublon : ' || rec.data);
WHEN OTHERS THEN
INSERT INTO import_log (msg)
VALUES ('Erreur : ' || rec.data || ' — ' || SQLERRM);
END;
END LOOP;
END;
$$;
À retenir absolument pour PostgreSQL 17 :
Dans les procédures PL/pgSQL, tu peux utiliser SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK, mais ROLLBACK TO SAVEPOINT — interdit.
Le "rollback partiel" dans les fonctions et procédures se fait uniquement avec des blocs imbriqués BEGIN ... EXCEPTION ... END.
Il vaut mieux gérer les transactions à l'extérieur (via les paramètres de connexion et autocommit), et à l'intérieur des procédures — utiliser les mécanismes de gestion d'erreur décrits ici.
GO TO FULL VERSION