aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Serveur de Jeu

MySQL Deadlock : ce que c'est et comment le résoudre

Un MySQL deadlock (interblocage) se produit lorsque deux transactions ou plus tentent de verrouiller les mêmes lignes dans un ordre opposé et finissent par s'attendre indéfiniment. InnoDB détecte cette impasse, désigne l'une des parties comme « victime » et l'annule ; votre application voit alors l'erreur Deadlock found when trying to get lock; try restarting transaction. Sur un serveur de jeu, un back-end e-commerce ou tout système à fort trafic d'écriture, cette erreur semble inévitable, mais la grande majorité des deadlocks peut être évitée grâce à une meilleure conception des transactions.

Qu'est-ce qu'un deadlock exactement ?

Le scénario classique est le suivant : la transaction A verrouille d'abord la ligne 1, puis demande la ligne 2. Au même moment, la transaction B a déjà verrouillé la ligne 2 et demande maintenant la ligne 1. Chacune attend un verrou détenu par l'autre, donc personne ne peut avancer. C'est une attente circulaire.

Une distinction importante : un deadlock n'est pas la même chose qu'un lock wait timeout. Dans un lock wait timeout, une seule transaction attend la libération d'un verrou et reçoit une erreur une fois innodb_lock_wait_timeout (50 secondes par défaut) expiré. Dans un deadlock, l'attente est circulaire : InnoDB n'attend donc pas du tout, il intervient immédiatement et annule l'une des parties.

Pourquoi les verrous InnoDB apparaissent-ils ?

InnoDB utilise le verrouillage au niveau des lignes, mais les verrous ne portent pas toujours sur « exactement une ligne ». Quelques comportements clés produisent des deadlocks :

  • UPDATE/DELETE sans index : si une clause WHERE ne peut pas utiliser d'index adapté, InnoDB verrouille toutes les lignes qu'il parcourt. Vous semblez modifier une ligne, mais vous en avez peut-être verrouillé des milliers.
  • Verrous de gap et next-key : au niveau d'isolation REPEATABLE READ (le défaut de MySQL), InnoDB verrouille non seulement les lignes mais aussi les « espaces » entre elles. Cela évite les lectures fantômes mais crée des conflits inattendus.
  • Vérifications de clés étrangères et d'index uniques : lors d'une insertion ou d'une mise à jour, InnoDB verrouille aussi les lignes de clé parent/enfant associées.
  • Ordre d'accès incohérent : les transactions qui mettent à jour les mêmes tables dans des ordres différents selon les chemins de code sont la cause la plus fréquente de deadlocks.

Lire le log de deadlock

Avant de deviner, lisez ce qu'InnoDB vous dit. Vous obtenez les détails du dernier deadlock avec :

SHOW ENGINE INNODB STATUS\G

Repérez la section LATEST DETECTED DEADLOCK dans la sortie. Elle montre les deux transactions, les verrous qu'elles détiennent (HOLDS THE LOCK(S)), les verrous qu'elles attendent (WAITING FOR THIS LOCK) et quelle requête s'exécutait. Voir quelle table et quel index ont été verrouillés, c'est la moitié de la solution.

Pour journaliser durablement chaque deadlock, ajoutez ceci à votre configuration MySQL :

[mysqld]
innodb_print_all_deadlocks = ON

Désormais, chaque deadlock atterrit dans le log d'erreurs MySQL, ce qui vous permet d'analyser même les conflits rares après coup.

Solution 1 : fixer un ordre de transaction cohérent

La plupart des deadlocks viennent d'un ordre de verrouillage incohérent. La solution est simple mais exige de la discipline : faire en sorte que chaque transaction accède aux ressources dans le même ordre, à chaque fois. Par exemple, lors d'un transfert de solde entre deux comptes, verrouillez toujours les lignes du plus petit id vers le plus grand :

START TRANSACTION;

-- Le plus petit id est toujours verrouillé en premier
UPDATE accounts SET balance = balance - 100
  WHERE id = LEAST(@from, @to);
UPDATE accounts SET balance = balance + 100
  WHERE id = GREATEST(@from, @to);

COMMIT;

Même si deux transactions touchent les deux mêmes lignes, aucune attente circulaire ne peut se former car elles avancent désormais dans le même ordre. Cette règle vous oblige à appliquer le même ordre d'accès partout dans votre code.

Solution 2 : garder des transactions petites et courtes

Plus une transaction reste ouverte longtemps, plus elle conserve ses verrous et plus le risque de conflit augmente. Règles pratiques :

  • Ne faites pas de requêtes HTTP, d'écriture de fichiers ou d'appels d'API externes à l'intérieur d'une transaction. Sortez le travail lent.
  • Ne placez dans une même transaction que les écritures qui doivent vraiment être atomiques.
  • N'utilisez pas SELECT ... FOR UPDATE sans nécessité ; ne verrouillez que la ligne que vous allez réellement mettre à jour.
  • Découpez les mises à jour en masse en petits lots (batches) plutôt qu'en une seule requête géante.

Solution 3 : ajouter les bons index

Une clause WHERE sans index force InnoDB à verrouiller bien plus de lignes que nécessaire. Vérifiez quel index utilise votre requête avec EXPLAIN :

EXPLAIN UPDATE orders SET status = 'shipped'
  WHERE customer_id = 42 AND status = 'paid';

Si la colonne type affiche ALL, un parcours complet de la table a lieu. Ajouter un index composite adapté sur customer_id et status réduit au minimum le nombre de lignes verrouillées et diminue fortement le risque de deadlock.

Solution 4 : réessayer côté application

Réduire les deadlocks à zéro est souvent irréaliste ; des conflits rares peuvent toujours survenir. Votre application doit donc capturer l'erreur de deadlock et réessayer la transaction. Avec Laravel, c'est intégré :

use Illuminate\Support\Facades\DB;

// Troisième argument : réessaie jusqu'à 3 fois en cas de deadlock
DB::transaction(function () {
    DB::table('accounts')->where('id', 1)->decrement('balance', 100);
    DB::table('accounts')->where('id', 2)->increment('balance', 100);
}, 3);

Point clé : réessayer n'a de sens que pour la partie désignée comme victime, et cela fonctionne quand la transaction est courte. Le retry n'est pas là pour masquer une mauvaise conception, mais pour lisser les conflits rares et inévitables.

Questions fréquentes

Un deadlock provoque-t-il une perte de données ?

Non. InnoDB annule entièrement la transaction victime, donc aucune modification à moitié terminée ne subsiste. Le vrai danger est que votre application avale l'erreur et ne réessaie jamais, de sorte que la modification de l'utilisateur disparaît silencieusement. C'est précisément pourquoi la logique de retry est essentielle.

Augmenter innodb_lock_wait_timeout résout-il les deadlocks ?

Non. Ce paramètre s'applique aux lock wait timeouts, pas aux deadlocks. InnoDB détecte et résout déjà les deadlocks instantanément ; augmenter le délai n'affecte que les scénarios de timeout et n'empêche pas les vrais deadlocks.

Utiliser un verrou de table met-il fin aux deadlocks ?

Techniquement, un verrou grossier peut réduire les conflits, mais il détruit la concurrence et ralentit fortement votre serveur. La bonne approche, ce sont des verrous de ligne pris dans un ordre cohérent avec des transactions courtes ; un verrou de table est presque toujours la mauvaise solution.

Des deadlocks récurrents sur votre serveur ? Nous pouvons analyser ensemble vos logs InnoDB et corriger votre ordre de transaction et vos index. Contactez-moi et rendons votre base de données stable.

Bu kategorideki tüm yazılar →

Devamı için