Aller au contenu principal

Transactions

Progression

#Transactions

ACID: Atomicité, Cohérence, Isolation, Durabilité. Les transactions encapsulent des modifications.

Objectifs d’apprentissage

  • Expliquer ACID avec des exemples concrets (journal, fsync, MVCC/verrous).
  • Choisir un niveau d’isolation en fonction des anomalies à éviter.
  • Identifier lectures sales, non répétables, phantoms et write‑skew.
sqlsql
1begin;2insert into users values (3, 'Charlie');3rollback; -- annule la modification4 5begin;6insert into users values (3, 'Charlie');7commit; -- valide la modification

#Playground

Chargement de l’éditeur...

#Animation: cycle de vie d’une transaction

LSN: 0
Journal (WAL)
Disque (après fsync)
Durabilité: COMMIT = append au journal + fsync. Recovery: rejouer le journal non appliqué.
BEGIN
Snapshot/version de départ selon SGBD
Modifications
Log/redo ; verrous ou MVCC ; isolation
COMMIT
Durabilité (fsync journal) ; publier versions
ROLLBACK
Annuler via le log en cas d’erreur

#Niveaux d’isolation

  • Read Uncommitted: peut voir des écritures non validées (rare, dangereux).
  • Read Committed: pas de lectures sales; possibles lectures non répétables.
  • Repeatable Read: empêche lectures non répétables; phantoms possibles (selon SGBD).
  • Serializable: équivalent à un ordre sériel; le plus sûr, potentiellement le plus coûteux.
MVCC vs verrous

PostgreSQL/SQLite utilisent principalement MVCC (versions et snapshots) pour éviter les blocages de lecture. D’autres SGBD se basent plus sur des verrous de lignes/plages. Le comportement des anomalies varie.

#Anomalies courantes (exemples)

  • Lectures sales: voir une écriture non validée (RU) → éviter en RC+.
  • Lectures non répétables: relecture retourne une valeur différente (RC) → éviter en RR+.
  • Phantoms: relecture d’un ensemble renvoie plus/moins de lignes (RR selon moteur) → éviter en Serializable.
  • Write‑skew: deux transactions valident ensemble des mises à jour compatibles individuellement mais violant une contrainte globale (souvent évité via verrous explicites ou Serializable).
Quand augmenter l’isolation ?

Lecture d’états financiers/rapports: RR/Serializable. Fort trafic d’updates: RC avec validations applicatives et verrous explicites ciblés.

#Bonnes pratiques

  • Rejouer en cas d’échec sérialisable: sous Serializable, des conflits légitimes entraînent des erreurs; implémentez des retries bornés.
  • Idempotence: rendez les opérations sûres à rejouer (clés uniques naturelles/synthétiques, INSERT ... ON CONFLICT DO NOTHING/UPDATE).
  • Petite surface: gardez les transactions courtes pour limiter la contention; évitez les appels réseau au milieu.
  • Outbox/Inbox: pour des messages/events consistants avec la BD, utilisez un pattern outbox (écrire l’événement dans la même transaction, puis publier de manière fiable).

#Cas classique: write‑skew (invariant brisé)

Invariant métier: « au moins une personne de garde ». Deux utilisateurs A et B décident de se retirer de la garde simultanément.

bg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 roundedbg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 rounded
1-- Table des gardes2create table if not exists garde(personne text primary key, de_garde boolean);3insert into garde(personne, de_garde) values ('A', true) on conflict(personne) do nothing;4insert into garde(personne, de_garde) values ('B', true) on conflict(personne) do nothing;

Sous isolation snapshot (RR/SSI), le scénario suivant peut briser l’invariant sans verrou explicite:

Session 1:

bg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 roundedbg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 rounded
1begin;2-- voit 2 en garde3select count(*) from garde where de_garde = true;  -- 24update garde set de_garde = false where personne = 'A';5-- commit plus tard

Session 2 (en parallèle):

bg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 roundedbg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 rounded
1begin;2select count(*) from garde where de_garde = true;  -- 2 (snapshot)3update garde set de_garde = false where personne = 'B';4commit;

Puis Session 1 commit → plus personne de garde. Correction:

  • Verrou explicite sur la ligne « sentinelle » ou sur l’ensemble concerné (SELECT … FOR UPDATE), puis re‑vérifier l’invariant.
  • Ou passer en SERIALIZABLE et gérer les rollbacks/retry.

Exemple avec verrou:

bg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 roundedbg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 rounded
1begin;2-- verrouiller les lignes participant à l’invariant3select * from garde where de_garde = true for update;4-- revérifier5select count(*) from garde where de_garde = true;  -- encore 26update garde set de_garde = false where personne = 'A';7commit;

#Diagramme: write‑skew (2 sessions)

Session 1
Session 2
SGBD
1. BEGIN ; SELECT count(de_garde)
2. BEGIN ; SELECT count(de_garde)
3. UPDATE A: de_garde=false
4. UPDATE B: de_garde=false
5. COMMIT (invariant brisé)
6. COMMIT (plus personne de garde)

#Correction (verrou / sérialisation)

Session 1
Session 2
SGBD
1. BEGIN ; SELECT ... FOR UPDATE
2. Locks acquis
3. BEGIN ; SELECT ... FOR UPDATE (bloque)
4. UPDATE A ; COMMIT
5. Débloqué ; relecture
6. Réévaluer puis UPDATE/COMMIT

#Exercice: lecture non répétable

  1. Session A: begin; select balance from accounts where id=1;
  2. Session B: begin; update accounts set balance=balance+10 where id=1; commit;
  3. Session A: select balance from accounts where id=1; → valeur différente en RC.

#Quiz

Quel niveau empêche lectures sales et non répétables, mais pas les phantoms ?
Quel niveau empêche lectures sales et non répétables, mais pas les phantoms ?