SERIALIZABLE — c'est le niveau d'isolation le plus élevé pour les transactions dans PostgreSQL. Ce niveau garantit que les résultats des transactions parallèles seront les mêmes que si elles étaient exécutées SÉQUENTIELLEMENT, l'une après l'autre. Du coup, aucune anomalie d'exécution concurrente (genre Dirty Read, Non-Repeatable Read, Phantom Read) ne peut arriver.
En gros, SERIALIZABLE assure un ordre parfait et une cohérence béton entre les transactions parallèles. C'est comme si PostgreSQL disait : "Tout le monde fait la queue, les gars !"
Pourquoi utiliser le niveau SERIALIZABLE ? Parfois, tu veux être sûr à 100% que tes données restent nickel, même si y'a des modifs en parallèle. Imagine une scène au supermarché, où les caissiers encaissent les clients en même temps. Si personne ne surveille l'ordre, tu pourrais te retrouver avec plus d'articles à la sortie que ce qui a été acheté. Avec SERIALIZABLE, ce genre de truc n'arrive jamais.
Exemple de config du niveau SERIALIZABLE
Pour mettre le niveau d'isolation SERIALIZABLE, il faut utiliser la commande :
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Par exemple, on va créer une transaction qui utilise ce niveau :
BEGIN; -- On démarre la transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- On définit le niveau d'isolation
SELECT * FROM products WHERE category = 'Électronique'; -- On récupère la liste des produits
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- On met à jour le stock
COMMIT; -- On valide les changements
Cas pratique : réservation de places de cinéma
Regardons un vrai exemple où le niveau SERIALIZABLE est juste indispensable. Imagine que tu développes un système de réservation de places de cinéma en ligne. Tes utilisateurs choisissent des sièges, et tu veux garantir qu'un même siège ne sera pas acheté par deux clients en même temps.
D'abord, on crée une table pour les sièges :
CREATE TABLE seats (
seat_id SERIAL PRIMARY KEY,
is_booked BOOLEAN DEFAULT FALSE
);
Maintenant, on ajoute quelques sièges :
INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);
Voici un exemple de transaction avec SERIALIZABLE.
Voilà comment tu peux faire une réservation safe d'un siège :
BEGIN; -- Début de la transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Niveau d'isolation SERIALIZABLE
-- On vérifie que le siège est libre
SELECT is_booked FROM seats WHERE seat_id = 1;
-- On réserve le siège
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;
COMMIT; -- On valide la réservation
Si une deuxième transaction parallèle essaie de réserver le même siège, PostgreSQL ne laissera pas faire et balancera une erreur de conflit de sérialisation.
Prévention du Phantom Read
Maintenant, parlons des "lectures fantômes" qu'on veut absolument éviter. Phantom Read arrive quand une transaction voit des changements de données ajoutées par une autre transaction pendant qu'elle bosse. Par exemple, ta transaction s'attend à un certain nombre de lignes, mais une autre transaction ajoute ou supprime des lignes, ce qui change le résultat.
Regarde cet exemple :
Données avant le début des transactions
| id | solde | utilisateur |
|---|---|---|
| 1 | 1000 | Alice |
| 2 | 500 | Bob |
Transaction 1
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Compter les utilisateurs avec un solde supérieur à 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;
-- On attend le résultat : 2 (Alice et Bob)
Transaction 2
Dans une autre session, une transaction parallèle s'exécute :
BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;
Retour à la Transaction 1
-- On refait la requête
SELECT COUNT(*) FROM accounts WHERE balance > 400;
Là, si tu n'utilises pas SERIALIZABLE, le résultat sera 3 au lieu de 2, parce que Charlie a été ajouté pendant l'exécution de la Transaction 1. C'est ça, le Phantom Read.
Mais avec SERIALIZABLE, PostgreSQL garantit que la Transaction 1 ne verra pas Charlie, parce que sa "vision du monde" est figée au début de la transaction.
Spécificités et limites du niveau SERIALIZABLE
On a vu comment SERIALIZABLE permet d'atteindre une isolation parfaite. Mais bon, rien n'est parfait sans quelques défauts, non ? Soyons honnêtes.
Baisse de performance
SERIALIZABLE demande beaucoup plus de ressources que les niveaux READ COMMITTED ou REPEATABLE READ. Pourquoi ? PostgreSQL doit simuler une exécution séquentielle des opérations, en surveillant tous les conflits possibles entre transactions.
Erreurs de sérialisation
Si PostgreSQL voit qu'il ne peut pas exécuter les transactions dans "l'ordre parfait", il balance une erreur de sérialisation (serialization_failure) et annule la transaction.
Exemple d'erreur :
ERROR: could not serialize access due to concurrent update
Pour gérer ça, tu peux relancer la transaction après l'échec :
DO $$
DECLARE
done BOOLEAN := FALSE;
BEGIN
WHILE NOT done LOOP
BEGIN
-- On démarre la transaction
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- On fait les opérations
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- On valide les changements
COMMIT;
done := TRUE; -- On sort de la boucle si tout est ok
EXCEPTION WHEN serialization_failure THEN
ROLLBACK; -- On annule en cas d'erreur
END;
END LOOP;
END;
$$;
C'est une approche classique dans les systèmes qui utilisent SERIALIZABLE.
Ce code est écrit en PL-SQL. On y reviendra plus tard. Je voulais juste te montrer un code stylé et qui marche. Et aussi te montrer à quoi sert PL-SQL :)
Quand utiliser SERIALIZABLE ?
Ce niveau d'isolation vaut le coup là où l'erreur coûte très cher :
- Transactions financières, genre traitement des paiements ou distribution de bonus.
- Systèmes de gestion de stock, pour éviter les commandes en double.
- Réservations en ligne, où il faut absolument éviter les conflits de réservation de ressources.
Si tu développes un système où les données doivent être 100% cohérentes, et que la perf passe après, SERIALIZABLE sera ton meilleur pote.
GO TO FULL VERSION