> Maîtriser la transaction SQL : principe ACID, BEGIN, COMMIT, ROLLBACK, savepoints, niveaux d'isolation et verrous. Guide complet avec l'exemple concret d'un transfert bancaire.

*Source : https://coder-studio.com/tutoriels/transaction-sql/*

---

[// Bases de données](https://coder-studio.com/tutoriels/)

# Les transactions SQL : ACID, COMMIT, ROLLBACK

Par **Kévin Papot** · publié le 14 octobre 2025

Imaginez un virement bancaire : débiter le compte A de 100 €, puis créditer le compte B de 100 €. Si le système tombe en panne entre ces deux opérations, l’argent disparaît — débité d’un côté, jamais arrivé de l’autre. La **transaction SQL** existe pour rendre ce scénario impossible : elle regroupe plusieurs instructions en un bloc indivisible qui réussit entièrement ou échoue entièrement, sans état intermédiaire.

Ce guide couvre le principe ACID, les commandes `BEGIN`, `COMMIT` et `ROLLBACK`, les savepoints, les niveaux d’isolation et les verrous, avec des exemples concrets tout du long.

## Le principe ACID

Une transaction respecte quatre propriétés résumées par l’acronyme **ACID**. Chacune répond à un risque précis.

### Atomicité

L’**atomicité** garantit que la transaction est un tout : ou bien toutes ses opérations sont appliquées, ou bien aucune. Il n’existe pas de « moitié de transaction ». Dans notre virement, impossible que le débit ait lieu sans le crédit.

### Cohérence

La **cohérence** assure que la base passe d’un état valide à un autre état valide. Les contraintes (clés étrangères, `CHECK`, unicité) sont respectées à la fin de la transaction. Si une opération violerait une règle, l’ensemble est annulé.

### Isolation

L’**isolation** garantit que deux transactions simultanées ne se marchent pas dessus. Chacune s’exécute comme si elle était seule, même quand des dizaines tournent en parallèle. Le degré d’isolation est réglable, comme nous le verrons.

### Durabilité

La **durabilité** assure qu’une fois la transaction validée (`COMMIT`), ses effets sont permanents — inscrits sur disque et résistants à une coupure de courant immédiatement après.

## BEGIN, COMMIT, ROLLBACK

Trois commandes structurent toute transaction. On ouvre avec `BEGIN` (ou `START TRANSACTION`), on valide avec `COMMIT`, on annule avec `ROLLBACK`.

```
BEGIN;
    UPDATE comptes SET solde = solde - 100 WHERE id = 'A';
    UPDATE comptes SET solde = solde + 100 WHERE id = 'B';
COMMIT;
```

Tant que le `COMMIT` n’est pas exécuté, les modifications restent invisibles pour les autres connexions et réversibles. Si un problème survient, `ROLLBACK` annule tout ce qui a été fait depuis le `BEGIN`.

```
BEGIN;
    UPDATE comptes SET solde = solde - 100 WHERE id = 'A';
    -- Contrôle applicatif : le solde est devenu négatif ?
    -- On décide d'annuler
ROLLBACK;
-- Le compte A retrouve son solde initial, rien n'est enregistré
```

Selon les moteurs, la syntaxe varie légèrement : MySQL utilise `START TRANSACTION`, PostgreSQL accepte `BEGIN` comme `START TRANSACTION`, et l’auto-commit (chaque instruction validée seule) est le comportement par défaut hors transaction explicite.

## L’exemple du transfert bancaire

Reprenons le virement de bout en bout, avec le contrôle métier qui justifie l’existence de la transaction.

```
CREATE TABLE comptes (
    id     TEXT PRIMARY KEY,
    solde  NUMERIC NOT NULL CHECK (solde >= 0)
);

INSERT INTO comptes VALUES ('A', 500), ('B', 200);

BEGIN;
    UPDATE comptes SET solde = solde - 100 WHERE id = 'A';
    UPDATE comptes SET solde = solde + 100 WHERE id = 'B';
COMMIT;
```

La contrainte `CHECK (solde >= 0)` est ici la gardienne de la cohérence. Si le compte A n’avait que 50 €, le premier `UPDATE` violerait la contrainte, déclencherait une erreur, et l’ensemble serait annulé — laissant les deux comptes intacts. Sans transaction, le crédit du compte B aurait pu passer même après l’échec du débit.

## Les savepoints : annuler partiellement

Un **savepoint** est un point de reprise à l’intérieur d’une transaction. Il permet d’annuler seulement une partie des opérations sans tout perdre. Utile dans une transaction longue où certaines étapes sont facultatives.

```
BEGIN;
    INSERT INTO commandes (id, client_id) VALUES (1, 42);

    SAVEPOINT avant_remise;
    UPDATE commandes SET remise = 10 WHERE id = 1;
    -- Finalement, on renonce à la remise
    ROLLBACK TO SAVEPOINT avant_remise;

    -- L'insertion de la commande est conservée
COMMIT;
```

Le `ROLLBACK TO SAVEPOINT` annule tout ce qui suit le savepoint, mais garde le reste. On peut libérer un savepoint devenu inutile avec `RELEASE SAVEPOINT avant_remise`. Cette granularité est précieuse dans les traitements par lots.

## Les niveaux d’isolation

L’isolation n’est pas tout ou rien : elle se règle par **niveaux**, du plus permissif au plus strict. Le compromis se fait entre exactitude et performance — plus l’isolation est forte, plus les transactions se bloquent mutuellement.

```
-- Régler le niveau pour la transaction en cours
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
```

Le standard SQL définit quatre niveaux, chacun autorisant ou interdisant certaines anomalies.

### READ UNCOMMITTED

Le plus permissif. Une transaction peut lire des données modifiées par une autre transaction **non encore validée** : c’est la _lecture sale_ (dirty read). Rarement souhaitable, il est même traité comme `READ COMMITTED` par PostgreSQL.

### READ COMMITTED

On ne lit que des données validées, ce qui élimine les lectures sales. C’est le niveau par défaut de PostgreSQL et d’Oracle. Reste possible la _lecture non répétable_ : relire la même ligne dans la transaction peut donner une valeur différente si une autre transaction l’a modifiée entre-temps.

### REPEATABLE READ

Une même ligne relue dans la transaction renvoie toujours la même valeur. C’est le niveau par défaut de MySQL (InnoDB). Il empêche les lectures non répétables ; selon les moteurs, il peut encore laisser passer les _lectures fantômes_ (de nouvelles lignes apparaissant dans un intervalle).

### SERIALIZABLE

Le plus strict. Les transactions se comportent comme si elles s’exécutaient **l’une après l’autre**, en série. Toutes les anomalies sont écartées, au prix de plus de blocages et d’éventuelles erreurs de sérialisation à gérer côté application.

Le tableau des anomalies évitées :

```
Niveau            | Lecture sale | Non répétable | Fantôme
------------------+--------------+---------------+--------
READ UNCOMMITTED  | possible     | possible      | possible
READ COMMITTED    | évitée       | possible      | possible
REPEATABLE READ   | évitée       | évitée        | possible*
SERIALIZABLE      | évitée       | évitée        | évitée
```

(\*PostgreSQL évite aussi les fantômes en REPEATABLE READ.)

## Les verrous

Pour appliquer l’isolation, la base pose des **verrous** (locks) sur les lignes ou les tables. Un verrou empêche une autre transaction de modifier — voire de lire — une donnée tant qu’il est tenu.

Vous pouvez verrouiller explicitement des lignes que vous comptez modifier, afin d’éviter qu’une transaction concurrente ne les change entre votre lecture et votre écriture.

```
BEGIN;
    -- Verrouille les lignes lues jusqu'au COMMIT
    SELECT solde FROM comptes WHERE id = 'A' FOR UPDATE;

    UPDATE comptes SET solde = solde - 100 WHERE id = 'A';
COMMIT;
```

Le `SELECT ... FOR UPDATE` réserve les lignes : toute autre transaction voulant les modifier attendra la fin de celle-ci. C’est le remède classique aux conditions de course sur un solde ou un stock.

### L’interblocage (deadlock)

Deux transactions peuvent se bloquer mutuellement : la première tient un verrou qu’attend la seconde, et inversement. C’est un **interblocage** (deadlock). Les bases le détectent et sacrifient l’une des transactions, qui reçoit une erreur et doit être relancée. Pour limiter le risque, acquérez toujours les verrous **dans le même ordre** dans toute l’application (par exemple, verrouiller toujours le compte au plus petit identifiant en premier).

## Bonnes pratiques

-   **Gardez les transactions courtes.** Une transaction ouverte longtemps retient ses verrous et bloque les autres. Faites vos calculs avant, ouvrez la transaction au dernier moment.
-   **Ne mettez pas d’appels réseau dans une transaction.** Attendre une API externe verrou en main est une source de blocages massifs.
-   **Gérez le rollback en cas d’erreur.** Côté application, entourez la transaction d’un bloc de gestion d’exception qui appelle `ROLLBACK` si une opération échoue.
-   **Choisissez le bon niveau d’isolation.** Restez sur le niveau par défaut sauf besoin précis ; passez à `SERIALIZABLE` pour les opérations financières sensibles, en acceptant de relancer sur erreur de sérialisation.

Les transactions vont souvent de pair avec des contraintes de cohérence entre tables ; pour bien poser ces règles, consultez notre article sur [les clés étrangères et l’intégrité référentielle](https://coder-studio.com/tutoriels/cle-etrangere-sql/).

## En résumé

Une transaction SQL regroupe des instructions en une unité atomique qui respecte les propriétés ACID. On l’ouvre avec `BEGIN`, on la valide avec `COMMIT`, on l’annule avec `ROLLBACK`, et les savepoints offrent une annulation partielle. Les niveaux d’isolation arbitrent entre exactitude et performance, tandis que les verrous et le `SELECT ... FOR UPDATE` protègent des accès concurrents. Gardez vos transactions courtes, gérez les erreurs par un rollback, et vos données resteront cohérentes même sous forte charge.

## À lire ensuite

-   [Les clés étrangères SQL : relations et intégrité](https://coder-studio.com/tutoriels/cle-etrangere-sql/)
-   [Les index SQL : accélérer ses requêtes](https://coder-studio.com/tutoriels/index-sql/)
-   [Les jointures SQL expliquées (INNER, LEFT, RIGHT…)](https://coder-studio.com/tutoriels/jointures-sql/)
