web-dev-qa-db-fra.com

Comment éviter mysql 'Deadlock trouvé lors d'une tentative de verrouillage; essayez de redémarrer la transaction '

J'ai une table innoDB qui enregistre les utilisateurs en ligne. Il est mis à jour à chaque actualisation de page par un utilisateur pour garder une trace de ses pages et de la date de leur dernier accès au site. J'ai ensuite un cron qui s'exécute toutes les 15 minutes pour supprimer d'anciens enregistrements.

J'ai trouvé une impasse quand j'essayais d'obtenir un verrou; essayez de redémarrer la transaction 'pendant environ 5 minutes la nuit dernière et cela semble être le cas lorsque vous exécutez des INSERT dans cette table. Quelqu'un peut-il suggérer comment éviter cette erreur?

=== EDIT ===

Voici les requêtes en cours d'exécution:

Première visite sur le site:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

A chaque actualisation de page:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

Cron toutes les 15 minutes:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Il fait ensuite quelques comptes pour enregistrer certaines statistiques (par exemple: membres en ligne, visiteurs en ligne).

228
David

Une astuce qui peut aider avec la plupart des blocages consiste à trier les opérations dans un ordre spécifique.

Vous obtenez un blocage lorsque deux transactions tentent de verrouiller deux verrous à des ordres opposés, à savoir:

  • connexion 1: serrure à clé (1), serrure à clé (2);
  • connexion 2: clé à serrure (2), clé à serrure (1);

Si les deux fonctionnent en même temps, la connexion 1 verrouille la clé (1), la connexion 2 verrouille la clé (2) et chaque connexion attend que l'autre relâche la clé -> impasse.

Maintenant, si vous avez modifié vos requêtes de telle sorte que les connexions verrouillent les clés dans le même ordre, à savoir:

  • connexion 1: serrure à clé (1), serrure à clé (2);
  • connexion 2: serrure à clé ( 1 ), serrure à clé ( 2 );

il sera impossible d'obtenir une impasse.

Alors voici ce que je suggère:

  1. Assurez-vous qu'aucune autre requête ne bloque l'accès à plus d'une clé à la fois, à l'exception de l'instruction delete. si vous le faites (et je suppose que vous le faites), indiquez leur ordre WHERE dans (k1, k2, .. kn) par ordre croissant.

  2. Corrigez votre instruction delete pour qu'elle fonctionne dans l'ordre croissant:

Changement 

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

À

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

Une autre chose à garder à l'esprit est que la documentation de MySQL suggère qu'en cas de blocage, le client devrait réessayer automatiquement. vous pouvez ajouter cette logique à votre code client. (Disons, 3 tentatives sur cette erreur particulière avant d'abandonner).

244
Omry Yadan

Une impasse se produit lorsque deux transactions s’attendent pour acquérir un verrou. Exemple: 

  • Tx 1: verrouiller A, puis B
  • Tx 2: verrou B, puis A

Il existe de nombreuses questions et réponses sur les blocages. Chaque fois que vous insérez/mettez à jour/ou supprimez une ligne, un verrou est acquis. Pour éviter un blocage, vous devez ensuite vous assurer que les transactions simultanées ne mettent pas à jour la ligne dans un ordre pouvant entraîner un blocage. De manière générale, essayez d’obtenir le verrou toujours dans le même ordre même dans une transaction différente (par exemple, toujours la table A en premier, puis la table B).

Une autre raison de blocage dans la base de données peut être les index manquants . Lorsqu'une ligne est insérée/mise à jour/supprimer, la base de données doit vérifier les contraintes relationnelles, c'est-à-dire s'assurer que les relations sont cohérentes. Pour ce faire, la base de données doit vérifier les clés étrangères dans les tables associées. Il se peut que entraîne l'acquisition d'un verrou autre que la ligne modifiée. Assurez-vous alors de toujours avoir l’index sur les clés étrangères (et bien sûr les clés primaires), sinon cela pourrait donner un verrou table au lieu d’un verrou rangée . Si le verrouillage de la table se produit, le conflit de verrouillage est plus élevé et la probabilité de blocage augmente.

61
ewernli

Il est probable que l'instruction delete affecte une grande partie du nombre total de lignes de la table. Cela pourrait éventuellement conduire à l’acquisition d’un verrou de table lors de la suppression. Garder un verrou (dans ce cas des verrous de ligne ou de page) et en acquérir davantage représente toujours un risque de blocage. Cependant, je ne peux pas expliquer pourquoi l'instruction insert conduit à une escalade de verrous - cela peut être dû au fractionnement/à l'ajout de page, mais quelqu'un connaissant mieux MySQL devra y répondre.

Pour commencer, il peut être intéressant d’essayer d’acquérir explicitement un verrou de table pour l’instruction delete. Voir TABLES DE VERROUILLAGE et Problèmes de verrouillage de table .

8
Anders Abel

Vous pouvez essayer de faire fonctionner ce travail delete en insérant d’abord la clé de chaque ligne à supprimer dans une table temporaire telle que ce pseudocode.

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

Le diviser de cette façon est moins efficace, mais vous évitez de garder un verrou de plage de clés pendant la delete

En outre, modifiez vos requêtes select pour ajouter une clause where en excluant les lignes de plus de 900 secondes. Cela évite la dépendance à la tâche cron et vous permet de la replanifier pour une exécution moins fréquente. 

Théorie sur les blocages: je n'ai pas beaucoup d'expérience dans MySQL, mais voici ... La variable delete va contenir un verrou de plage de clés pour datetime, afin d'empêcher l'ajout de lignes correspondant à sa clause where au milieu de la transaction et au fur et à mesure qu'elle trouve des lignes à supprimer, elle tentera d'acquérir un verrou sur chaque page en cours de modification. La insert va acquérir un verrou sur la page dans laquelle elle est insérée, et then tente d'acquérir le verrou à clé. Normalement, la insert attend patiemment que le verrou de clé s'ouvre, mais cela se bloquera si delete tente de verrouiller la même page que la insert utilise car la delete a besoin de ce verrou de page et que insert en a besoin. Cela ne semble pas correct pour les insertions, cependant, les variables delete et insert utilisent des plages de date/heure qui ne se chevauchent pas. Il se peut donc que quelque chose d'autre se passe.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html

6
Brian Sandlin

Pour les programmeurs Java utilisant Spring, j'ai évité ce problème en utilisant un aspect AOP qui réessaie automatiquement les transactions qui se heurtent à des impasses transitoires.

Voir @RetryTransaction Javadoc pour plus d’informations.

1
Archie

Si quelqu'un est toujours aux prises avec ce problème:

J'ai fait face à un problème similaire où 2 demandes frappaient le serveur en même temps. Il n'y avait pas de situation comme ci-dessous:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

Je me demandais donc pourquoi une impasse se produisait. 

Ensuite, j'ai découvert qu'il existait une relation parent-enfant entre 2 tables en raison de la clé étrangère. Lors de l'insertion d'un enregistrement dans une table enfant, la transaction acquérait un verrou sur la ligne de la table parent. Immédiatement après, j’essayais de mettre à jour la ligne parente qui provoquait l’élévation du verrou à EXCLUSIVE. Comme la deuxième transaction simultanée détenait déjà un verrou SHARED, cela entraînait un blocage.

Voir: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html

0
chatsap

J'ai une méthode dont les composants internes sont encapsulés dans une transaction MySqlTransaction.

La question de l'impasse est apparue lorsque j'ai utilisé la même méthode en parallèle avec elle-même.

Il n'y a pas eu de problème d'exécution d'une seule instance de la méthode.

Lorsque j'ai supprimé MySqlTransaction, j'ai pu exécuter la méthode en parallèle avec elle-même, sans aucun problème.

Tout en partageant mon expérience, je ne préconise rien.

0
CINCHAPPS