Database & Architecture
Thème :
Junior
1 vues ·4/12 leçonsNiveaux d’Isolation & Verrouillage
Les niveaux d’isolation contrôlent comment les transactions interagissent entre elles.
Les Niveaux d’Isolation
<?php
// Définir le niveau d'isolation (MySQL)
$pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
$pdo->beginTransaction();
// Niveaux du moins au plus restrictif :
// READ UNCOMMITTED - Voir les changements non validés (lectures sales)
// READ COMMITTED - Voir seulement les données validées
// REPEATABLE READ - La même requête retourne les mêmes résultats (défaut MySQL)
// SERIALIZABLE - Isolation complète (le plus lent)
Les Problèmes de Concurrence
Lecture Sale (Dirty Read)
<?php
// Transaction A (READ UNCOMMITTED)
// Voit les changements non validés de la Transaction B
// Si B est annulée, A a lu des données invalides !
Lecture Non-Répétable (Non-Repeatable Read)
<?php
// Transaction A lit une ligne, obtient 100 €
// Transaction B met à jour la ligne à 50 €, valide
// Transaction A relit, obtient 50 €
// Même requête, résultat différent !
Lecture Fantôme (Phantom Read)
<?php
// Transaction A compte les lignes WHERE status = 'active', obtient 10
// Transaction B insère une nouvelle ligne active, valide
// Transaction A recompte, obtient 11
// De nouvelles lignes sont apparues !
Le Verrouillage de Lignes
<?php
$pdo->beginTransaction();
// SELECT ... FOR UPDATE verrouille les lignes
$stmt = $pdo->prepare(
'SELECT balance FROM accounts WHERE id = :id FOR UPDATE'
);
$stmt->execute(['id' => 1]);
$balance = $stmt->fetchColumn();
// D'autres transactions attendent ici jusqu'à notre commit/rollback
if ($balance >= $amount) {
$stmt = $pdo->prepare(
'UPDATE accounts SET balance = balance - :amount WHERE id = :id'
);
$stmt->execute(['amount' => $amount, 'id' => 1]);
}
$pdo->commit(); // Verrou libéré
Le Verrouillage Optimiste
<?php
class OptimisticLockException extends Exception {}
// Ajouter une colonne version à la table
// version INT DEFAULT 1
function updateWithOptimisticLock(PDO $pdo, int $id, array $data): void {
// Lire la version actuelle
$stmt = $pdo->prepare('SELECT version FROM products WHERE id = :id');
$stmt->execute(['id' => $id]);
$version = $stmt->fetchColumn();
// Mettre à jour seulement si la version correspond
$stmt = $pdo->prepare(
'UPDATE products
SET name = :name, price = :price, version = version + 1
WHERE id = :id AND version = :version'
);
$stmt->execute([
'id' => $id,
'name' => $data['name'],
'price' => $data['price'],
'version' => $version,
]);
if ($stmt->rowCount() === 0) {
throw new OptimisticLockException('L\'enregistrement a été modifié par un autre processus');
}
}
Exemple Concret
Un gestionnaire de transactions avec support du verrouillage
<?php
declare(strict_types=1);
// Gestionnaire de transactions avec rollback automatique
class TransactionManager {
public function __construct(
private PDO $pdo
) {}
/**
* @template T
* @param callable(): T $callback
* @return T
*/
public function transaction(callable $callback): mixed {
$this->pdo->beginTransaction();
try {
$result = $callback();
$this->pdo->commit();
return $result;
} catch (Throwable $e) {
$this->pdo->rollBack();
throw $e;
}
}
public function withLock(string $table, int $id, callable $callback): mixed {
return $this->transaction(function() use ($table, $id, $callback) {
// Acquérir un verrou sur la ligne
$stmt = $this->pdo->prepare(
"SELECT * FROM $table WHERE id = :id FOR UPDATE"
);
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
if (!$row) {
throw new RuntimeException('Enregistrement introuvable');
}
return $callback($row);
});
}
}
// Utilisation
$txManager = new TransactionManager($pdo);
// Transaction simple
$orderId = $txManager->transaction(function() use ($pdo, $items) {
$pdo->prepare('INSERT INTO orders (user_id) VALUES (:user_id)')
->execute(['user_id' => $userId]);
$orderId = $pdo->lastInsertId();
foreach ($items as $item) {
$pdo->prepare('INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)')
->execute([$orderId, $item['product_id'], $item['quantity']]);
}
return $orderId;
});
// Avec verrouillage de ligne
$txManager->withLock('accounts', 1, function($account) use ($pdo, $amount) {
if ($account['balance'] < $amount) {
throw new InsufficientFundsException();
}
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
->execute([$amount, $account['id']]);
});
?>
📘 Cette leçon fait partie du cours PHP & Bases de Données Relationnelles sur la plateforme d’apprentissage RostoDev.
Prêt à passer à la suite ?
Vous avez terminé cette leçon sur Niveaux d'Isolation & Verrouillage. Continuez votre apprentissage avec la prochaine leçon ou explorez d'autres formations.