Dans les volets précédents, nous avons vu les bases des transactions, leur rôle, les systèmes qui les supportent, et comment les implémenter.
Mais dans les vraies applications, surtout à grande échelle, les choses se compliquent :
tu risques de rencontrer des erreurs subtiles, des blocages (deadlocks), ou des performances dégradées.
👉 Ce volet est donc dédié à la gestion avancée des transactions.
🔁 1. Gérer les erreurs dans une transaction
Les erreurs peuvent provenir de multiples sources pendant une transaction :
- ❌ Problème de validation métier
- ❌ Conflit avec une autre transaction (concurrence)
- ❌ Échec de connexion à un service tiers
- ❌ Requête SQL mal formée ou contrainte violée (clé étrangère, unique…)
💡 Que faire ?
Toujours encapsuler les transactions dans un bloc try/catch, surtout quand elles sont critiques :
1try {2 DB::beginTransaction();34 // ... opérations critiques56 DB::commit();7} catch (\Throwable $e) {8 DB::rollBack();9 Log::error("Transaction failed: " . $e->getMessage());10 throw $e;11}1try {2 DB::beginTransaction();34 // ... opérations critiques56 DB::commit();7} catch (\Throwable $e) {8 DB::rollBack();9 Log::error("Transaction failed: " . $e->getMessage());10 throw $e;11}
🚨 N'oublie pas de relancer l'exception si elle doit remonter au frontend ou à un service.
💥 2. Comprendre et éviter les deadlocks
❓ C’est quoi un deadlock ?
Un deadlock (verrou mortel) survient lorsque deux transactions s’attendent mutuellement pour libérer une ressource. Résultat : elles restent bloquées l’une l’autre indéfiniment (ou jusqu'à l’intervention du moteur SQL).
🔍 Exemple typique :
- Transaction A verrouille la ligne X
- Transaction B verrouille la ligne Y
- A veut modifier Y (verrouillé par B)
- B veut modifier X (verrouillé par A) ➡️ Blocage mutuel !
⚠️ Symptôme dans Laravel
Tu peux voir une erreur comme :
1SQLSTATE[40001]: Deadlock found when trying to get lock; try restarting transaction1SQLSTATE[40001]: Deadlock found when trying to get lock; try restarting transaction
🛠️ Comment éviter les deadlocks ?
✔️ 1. Toujours accéder aux ressources dans le même ordre
Par exemple, si tu modifies deux tables ensemble (users et orders), modifie-les toujours dans le même ordre, quelle que soit la requête.
1// Mauvais2$repo->updateOrder($id);3$repo->updateUser($userId);45// Bon (toujours dans cet ordre)6$repo->updateUser($userId);7$repo->updateOrder($id);1// Mauvais2$repo->updateOrder($id);3$repo->updateUser($userId);45// Bon (toujours dans cet ordre)6$repo->updateUser($userId);7$repo->updateOrder($id);
✔️ 2. Gérer la concurrence avec des LOCK
Pour des opérations critiques, tu peux verrouiller explicitement :
1DB::table('accounts')->where('id', $id)->lockForUpdate()->first();1DB::table('accounts')->where('id', $id)->lockForUpdate()->first();
✔️ 3. Limiter la durée des transactions
Plus une transaction est longue, plus elle a de chances de causer un deadlock ou un blocage.
❌ Ne fais pas de requête API, de traitement lourd ou de logique métier trop complexe à l'intérieur d’une transaction.
✔️ 3. Limiter la durée des transactions
Plus une transaction est longue, plus elle a de chances de causer un deadlock ou un blocage.
❌ Ne fais pas de requête API, de traitement lourd ou de logique métier trop complexe à l'intérieur d’une transaction.
1DB::transaction(function () {2 // ...3}, $attempts = 5);1DB::transaction(function () {2 // ...3}, $attempts = 5);
Cela réessaye automatiquement en cas de deadlock !
🧠 3. Optimiser les performances des transactions
⚡ 1. Ne verrouille que ce qui est nécessaire
Évite les SELECT * ou les mises à jour massives. Préfère cibler les lignes exactes, pour que les verrous soient plus petits et relâchés plus vite.
1// Mieux2DB::table('users')->where('id', $id)->update(['status' => 'active']);1// Mieux2DB::table('users')->where('id', $id)->update(['status' => 'active']);
⚡ 2. Indexe correctement les colonnes utilisées dans WHERE
Des requêtes lentes = des verrous longs = des risques de blocage.
🚀 Indices = vitesse + moins de conflits
⚡ 3. Préfère les transactions courtes
Découpe les traitements complexes :
1// Mauvais2DB::transaction(function () {3 // boucle lente + logique + notifications + update4});56// Mieux : séparer7$data = collect([...])->map(...);8DB::transaction(function () use ($data) {9 foreach ($data as $item) {10 // update simple11 }12});1// Mauvais2DB::transaction(function () {3 // boucle lente + logique + notifications + update4});56// Mieux : séparer7$data = collect([...])->map(...);8DB::transaction(function () use ($data) {9 foreach ($data as $item) {10 // update simple11 }12});
📊 4. Benchmarks : Quel SGBD est le plus performant ?
| SGBD | Gestion des deadlocks | Performance transactionnelle | Recommandé pour |
|---|---|---|---|
| MySQL | Bonne (InnoDB) | Très rapide | Web apps courantes |
| PostgreSQL | Excellente (MVCC) | Très stable et cohérente | Applications métier |
| SQL Server | Très solide | Optimisé pour gros volumes | Apps d’entreprise |
📌 Laravel fonctionne parfaitement avec les trois !
✅ Résumé : bonnes pratiques à retenir
- Toujours capturer les erreurs dans une transaction
- Attention aux deadlocks : ordre des accès, verrou explicite, retries
- Optimise les accès : index, portée réduite, durée courte
- Teste les comportements en charge ou en cas de conflit