← PostgreSQL 18 — ingénierie des données

Module 3 / 8 · 90 min

MVCC, isolation, verrous et reprise

Protéger une décision lorsque plusieurs transactions agissent ensemble.

Votre progression apparaîtra ici lorsque JavaScript est actif.

La progression et le meilleur score sont enregistrés uniquement dans ce navigateur. Aucun compte n’est nécessaire ; effacer les données du navigateur les supprime. Les durées de modules sont indicatives et incluent les exercices.

Comprendre le mécanisme

PostgreSQL emploie MVCC pour exposer des versions cohérentes des données. En READ COMMITTED, des instructions successives peuvent observer des instantanés différents. Une transaction ne garantit donc pas que toute décision lecture-puis-écriture conserve un invariant partagé. Un verrou de ligne ou une écriture conditionnelle peut protéger un cas précis ; une contrainte doit compléter la protection lorsque la règle peut y être exprimée.

Raisonner et vérifier

SERIALIZABLE peut annuler une transaction incompatible avec une exécution sérielle ; la reprise doit rejouer la transaction complète et recalculer la décision. Les deadlocks exigent aussi une politique de reprise et un ordre de prise de verrous cohérent. Dans vos tests, utilisez deux connexions réelles et des points de synchronisation afin de rendre l’entrelacement observable.

Un snapshot par instruction en Read Committed

Dans PostgreSQL, un SELECT ordinaire en Read Committed voit normalement un état validé au début de l’instruction. Un second SELECT de la même transaction peut donc voir une validation concurrente plus récente. BEGIN ne suffit pas à promettre une photographie unique pour toute la transaction. Représentez les lectures et commits sur une chronologie pour distinguer l’instant du snapshot de celui de la réponse réseau.

Erreur fréquente à comprendre

Deux SELECT successifs voient nécessairement le même état concurrent.

Une autre transaction peut valider entre les instructions.

Read Uncommitted dans ce moteur

Le nom d’un niveau d’isolation doit être interprété avec les garanties réelles du moteur ciblé. PostgreSQL traite Read Uncommitted comme Read Committed plutôt que fournir des lectures sales par cette option. Ne recopiez pas aveuglément un tableau générique SQL dans une analyse de production. Vérifiez la version et les opérations étudiées, puis testez ce qui peut effectivement devenir visible entre deux sessions.

Erreur fréquente à comprendre

Read Uncommitted PostgreSQL lit obligatoirement les écritures non validées.

Le moteur n’expose pas ces lectures sales par ce niveau.

Les limites de Repeatable Read

Le modèle PostgreSQL Repeatable Read fournit une stabilité plus forte et évite notamment les lectures fantômes dans son implémentation. Il ne rend pas tout invariant métier sérialisable. Deux transactions peuvent lire un état commun puis modifier des lignes différentes de façon incompatible avec une règle globale. L’exemple des deux médecins de garde montre ce write skew : chaque écriture individuelle semble valide alors que leur combinaison viole l’invariant.

Erreur fréquente à comprendre

Repeatable Read PostgreSQL garantit toute sérialisation métier.

Des anomalies de sérialisation peuvent encore nécessiter un niveau supérieur.

Reprendre une transaction sérialisable

Serializable vise un résultat compatible avec un ordre séquentiel, mais peut refuser une transaction lorsque l’exécution ne peut pas préserver cette propriété. Après un échec de sérialisation, il faut reprendre la transaction complète avec ses lectures et décisions, pas seulement la dernière écriture. Borner les reprises et traiter les effets externes évite qu’une garantie de base crée des doublons ou une boucle de charge illimitée.

Erreur fréquente à comprendre

Serializable signifie qu’aucune transaction ne peut échouer.

Le moteur peut rejeter un ordonnancement incompatible.

Maintenir le verrou pendant la décision

Un verrou utile doit couvrir la lecture, la décision et l’écriture qui partagent l’invariant. Le prendre après le calcul ou le libérer avant la transition peut laisser une fenêtre de concurrence. Choisissez une ressource de coordination commune à tous les chemins concernés. Une stratégie suivie seulement par la moitié des transactions ne démontre pas la règle globale ; les participants doivent respecter le même protocole.

Erreur fréquente à comprendre

FOR UPDATE protège indéfiniment après toute fin de transaction.

La durée de protection dépend du cycle transactionnel.

Ordre des verrous et attente cyclique

Deux transactions peuvent attendre chacune une ressource détenue par l’autre. Un ordre cohérent d’acquisition réduit certaines possibilités de deadlock sans prétendre éliminer tous les échecs de concurrence. Définissez cet ordre dans les opérations multi-objets et testez les chemins opposés. Le traitement d’erreur doit rester prévu : un blocage détecté n’est pas une autorisation de valider un état partiellement calculé.

Erreur fréquente à comprendre

Toute combinaison de verrous finit nécessairement sans attente cyclique.

L’ordre et les dépendances comptent.

Effets distants et reprise idempotente

Un rollback SQL n’annule pas un email déjà envoyé ni un appel PSP validé. Organisez les effets externes selon une frontière durable, par exemple avec une référence d’opération et une publication coordonnée. Une reprise de transaction doit pouvoir distinguer une nouvelle intention d’une répétition. Le test important simule la perte de réponse après l’effet, car l’absence de réponse ne démontre pas l’absence de mutation distante.

Erreur fréquente à comprendre

Un rollback SQL annule automatiquement un email déjà envoyé.

Les effets distants ont une autre frontière de commit.

Une séquence n’est pas un compteur sans trous

Une valeur allouée par une séquence peut rester consommée après l’annulation d’une transaction. Des écarts ne prouvent donc pas automatiquement une suppression frauduleuse de ligne. Si le domaine exige une numérotation particulière, concevez cette règle explicitement au lieu de la déduire d’un identifiant technique. Les tests doivent séparer unicité, ordre apparent et absence de trous : ce sont des propriétés différentes.

Erreur fréquente à comprendre

Tout rollback remet toutes les séquences à leur valeur précédente.

Les séquences n’ont pas ce comportement transactionnel général.

Construire un test à deux connexions

Pour démontrer un invariant concurrent, synchronisez deux sessions aux points pertinents : lecture commune, décision, écriture puis validation. Un appel après l’autre ne reproduit pas la course. Vérifiez l’état final et le résultat de chaque demande. Pour deux réservations de remboursement de 7 000 sur une capacité de 10 000, il faut observer qu’une stratégie choisie empêche les deux succès, pas seulement qu’un calcul isolé est correct.

Erreur fréquente à comprendre

Un parcours séquentiel démontre tout conflit possible.

Les entrelacements doivent être provoqués.

Limiter la durée des transactions

Une transaction conservée ouverte peut retenir verrous, snapshots et ressources, même lorsqu’un client ne travaille plus. Gardez les opérations nécessaires dans la frontière et déplacez les attentes externes lorsqu’un contrat cohérent le permet. Définissez délais et annulation selon le produit. Une durée courte n’est pas une preuve de correction métier, mais une transaction indéfinie augmente les coûts et rend les incidents de concurrence plus difficiles à diagnostiquer.

Erreur fréquente à comprendre

Laisser une transaction ouverte est sans effet sur le système.

Snapshots, verrous et maintenance peuvent être affectés.

Atelier guidé : lire, expliquer et vérifier le code

BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT amount FROM lab_accounts WHERE id = 1;
-- Une autre session valide amount = 900 ici.
SELECT amount FROM lab_accounts WHERE id = 1;
COMMIT;

Dans la chronologie de l’atelier, une autre session valide une modification entre deux SELECT. Avec Read Committed, le second SELECT peut observer ce nouvel état. Les lectures appartiennent à la même transaction explicite, mais ne promettent pas le même snapshot pour toute sa durée. L’exemple ne montre pas non plus un verrou exclusif global empêchant l’autre session de travailler.

Reproduisez la chronologie avec deux connexions de laboratoire et notez les commits. Comparez ensuite une stratégie d’isolation plus forte ou de verrouillage adaptée à un invariant précis. Le choix doit être évalué avec ses refus et reprises possibles. Une amélioration de stabilité des lectures ne doit pas être présentée comme une atomicité distribuée couvrant automatiquement les effets d’un service externe.

Write skew : préserver au moins un médecin de garde

Sous REPEATABLE READ PostgreSQL, chaque transaction peut observer un instantané cohérent sans que toutes les décisions concurrentes préservent un invariant portant sur plusieurs lignes. Deux médecins de garde sont visibles ; chaque transaction retire un médecin différent et pense laisser l’autre présent. Les écritures ne visent pas la même ligne, ce qui rend insuffisant le seul raisonnement sur conflits d’écriture directs.

Si toutes ces opérations suivent SERIALIZABLE, PostgreSQL peut refuser une exécution incompatible avec un ordre sériel. L’application doit alors reprendre toute la transaction : nouvelle lecture, nouveau calcul et nouvelle décision. Rejouer seulement le dernier UPDATE conserverait une décision prise sur un état périmé.

Bornez les tentatives et gérez séparément les effets externes qui ne peuvent pas être rejoués naïvement. Dans un test concurrent synchronisé, vérifiez l’invariant final et le traitement de l’erreur de sérialisation, pas seulement le succès apparent d’une connexion isolée.

Points de vigilance : Une contrainte sur une valeur de ligne ne démontre pas un invariant portant sur plusieurs médecins. Ce niveau ne supprime pas l’anomalie lecture-décision-écriture décrite. Rejouer seulement UPDATE conserve une décision prise sur une lecture devenue invalide. PostgreSQL peut annuler une transaction incompatible avec une exécution sérielle ; la reprise doit relire et recalculer.

Documentation de référence ↗

Remboursements concurrents : réserver sans dépasser le capturé

L’invariant porte sur le cumul réservé, pas seulement sur chaque demande isolée. Avec 10000 capturés, deux lectures séparées de zéro peuvent chacune autoriser 7000 puis produire un total incompatible. Un contrôle applicatif avant écriture laisse donc une fenêtre de concurrence.

Dans le scénario READ COMMITTED posé, utilisez une mise à jour atomique de la même ligne : incrémenter reserved uniquement lorsque reserved + montant reste inférieur ou égal à captured. Vérifiez qu’une ligne a été modifiée. L’écriture concurrente attend puis réévalue le prédicat sur la version actualisée ; la seconde réservation de 7000 ne peut alors pas réussir.

Testez deux connexions synchronisées et inspectez résultat de chaque UPDATE et cumul final. Traitez aussi l’identité des demandes pour les doublons et la frontière PSP : réserver localement n’est pas encore un remboursement externe confirmé. Les montants et le capturé immuable sont des hypothèses explicites de cet exercice.

Points de vigilance : Deux lectures peuvent voir le même disponible et approuver chacune 7 000. Des verrous locaux à des processus différents ne protègent pas une ressource partagée. L’écriture concurrente attend puis réévalue la condition sur la ligne actualisée. Une seule réservation peut passer ici. L’unicité des demandes ne protège pas une limite globale de montant.

Documentation de référence ↗

Exemple commenté

BEGIN;
UPDATE lab_stock
SET quantity = quantity - 2
WHERE id = 1 AND quantity >= 2
RETURNING quantity;
-- Enregistrer la réservation liée, puis COMMIT ou ROLLBACK.

Une absence de ligne retournée indique ici que la condition n’a pas permis le débit. Le reste de la réservation doit partager la même transaction si les effets sont indissociables.

Exercice de mise en pratique

Reproduisez une double réservation sur deux sessions et montrez que le stock reste non négatif.

Voir une proposition de correction

Préparez un stock limité, synchronisez les deux demandes et utilisez l’écriture conditionnelle. Vérifiez une seule réservation lorsque la capacité est insuffisante pour les deux ; annulez les tests dans le laboratoire.

Points de vérification

  • Recalculer après un échec de sérialisation.
  • Observer le nombre de lignes modifiées.
  • Documenter l’ordre des verrous.

Contrôle de connaissances

Cours associé : MVCC, isolation, verrous et reprise ↗
En READ COMMITTED, deux SELECT successifs d’une transaction voient-ils nécessairement le même instantané ?

Documentation de référence ↗

Validation du module

101 questions, objectif pédagogique de 75 %. Durée conseillée de 150 minutes, sans limite imposée. Les questions et les choix sont mélangés ; le corrigé apparaît après validation. Le contrôle rapide de la leçon et l’examen final du cours restent des exercices distincts.

Commencer ou reprendre les 101 questions →