Mysql
 sql >> Base de données >  >> RDS >> Mysql

MySQL :Obtention permanente du verrouillage des métadonnées de la table en attente

La solution acceptée est malheureusement erronée . C'est juste dans la mesure où il dit,

C'est en effet (presque sûrement; voir ci-dessous) quoi faire. Mais ensuite, il suggère,

...et 1398 n'est pas la connexion avec la serrure. Comment est-ce possible? 1398 est la connexion en attente pour la serrure. Cela signifie qu'il n'a pas encore la serrure, et par conséquent, la tuer ne sert à rien. Le processus détenant le verrou conservera toujours le verrou, et le suivant thread essayant de faire quelque chose sera donc aussi décrochage et entrez "En attente de verrouillage des métadonnées" dans l'ordre.

Vous n'avez aucune garantie que les processus "en attente de verrouillage des métadonnées" (WFML) ne se bloqueront pas également, mais vous pouvez être certain que tuer uniquement les processus WFML ne produira exactement rien .

La vraie cause est qu'un autre processus détient le verrou , et plus important encore, SHOW FULL PROCESSLIST ne vous dira pas directement de quoi il s'agit .

Cela VA vous dire si le processus est en cours quelque chose, oui. Habituellement, cela fonctionne. Ici, le processus qui détient le verrou ne fait rien , et se cache parmi d'autres threads sans rien faire.

Dans ce cas, le coupable est presque certainement processus 1396 , qui a commencé avant le processus 1398 et est maintenant en Sleep état, et a été pendant 46 secondes. Puisque 1396 a clairement fait tout ce qu'il devait faire (comme le prouve le fait qu'il est maintenant en veille, et ce depuis 46 secondes, en ce qui concerne MySQL ), aucun thread ne s'étant endormi avant cela n'aurait pu détenir un verrou (ou 1396 aurait également calé).

IMPORTANT  :si vous vous êtes connecté à MySQL en tant qu'utilisateur limité, SHOW FULL PROCESSLIST ne sera pas montrer tous les processus. Ainsi, le verrou peut être détenu par un processus que vous ne voyez pas.

Un meilleur SHOW PROCESSLIST

SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
    FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
    AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
    ORDER BY `DB`, `TIME` DESC

Ce qui précède peut être réglé pour afficher uniquement les processus en état de sommeil, et de toute façon il les triera par ordre décroissant de temps, il est donc plus facile de trouver le processus qui se bloque (il s'agit généralement du Sleep 'ing un immédiatement avant ceux "en attente de verrouillage des métadonnées").

La chose importante

Laissez tout processus "en attente de verrouillage des métadonnées" seul .

Solution rapide et sale, pas vraiment recommandée mais rapide

Tuez tous processus en état "Veille", sur la même base de données, qui sont plus anciens que les plus anciens thread dans l'état "en attente de verrouillage des métadonnées". C'est ce que Arnaud Amaury aurait fait :

  • pour chaque base de données qui a au moins un thread dans WaitingForMetadataLock :
    • la connexion la plus ancienne dans WFML sur cette base de données date de Z secondes
    • TOUS les threads "Sleep" sur cette base de données et plus anciens que Z doivent disparaître. Commencez par les plus récents, juste au cas où.
    • Si une connexion plus ancienne et non endormie existe sur cette base de données, alors c'est peut-être celle qui détient le verrou, mais elle fait quelque chose . Vous pouvez bien sûr le tuer, mais surtout s'il s'agit d'une mise à jour/insertion/suppression, vous le faites à vos risques et périls.

Quatre-vingt-dix-neuf fois sur cent, le fil à tuer est le plus jeune parmi ceux en état de veille qui sont plus âgés que l'ancien en attente de verrouillage des métadonnées :

TIME     STATUS
319      Sleep
205      Sleep
 19      Sleep                      <--- one of these two "19"
 19      Sleep                      <--- and probably this one(*)
 15      Waiting for metadata lock  <--- oldest WFML
 15      Waiting for metadata lock
 14      Waiting for metadata lock

(*) l'ordre TIME a en fait des millisecondes, ou du moins on m'a dit, il ne les affiche tout simplement pas. Ainsi, bien que les deux processus aient une valeur de temps de 19, le plus bas devrait être plus jeune.

Correction plus ciblée

Exécutez SHOW ENGINE INNODB STATUS et regardez la section "TRANSACTION". Vous trouverez, entre autres, quelque chose comme

TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;

Maintenant, vous vérifiez avec SHOW FULL PROCESSLIST que fait l'ID de thread 1396 avec sa transaction #1701. Il y a de fortes chances qu'il soit en état "Sommeil". Donc :une transaction active (#1701) avec un verrou actif, elle a même fait quelques changements car elle a une entrée de journal d'annulation... mais est actuellement inactive. Ça et aucun autre n'est le fil que vous devez tuer. Perdre ces modifications.

N'oubliez pas que ne rien faire dans MySQL ne signifie pas ne rien faire en général. Si vous obtenez des enregistrements de MySQL et créez un CSV pour le téléchargement FTP, pendant le téléchargement FTP, la connexion MySQL est inactive.

En fait, si le processus utilisant MySQL et le serveur MySQL se trouvent sur la même machine, que cette machine exécute Linux et que vous disposez des privilèges root, il existe un moyen de savoir quel processus a la connexion qui a demandé le verrou. Cela permet à son tour de déterminer (à partir de l'utilisation du processeur ou, au pire, de strace -ff -p pid ) si ce processus est vraiment faire quelque chose ou non, pour aider à décider s'il est sûr de tuer.

Pourquoi cela se produit-il ?

Je constate que cela se produit avec les applications Web qui utilisent des connexions MySQL "persistantes" ou "groupées", ce qui, de nos jours, fait généralement gagner très peu de temps :l'instance de l'application Web s'est terminée, mais la connexion ne l'a pas été , donc son verrou est toujours actif... et bloque tout le monde.

Une autre façon intéressante que j'ai trouvé est, dans les hypothèses ci-dessus, d'exécuter une requête renvoyant certaines lignes, et de n'en récupérer que certaines . Si la requête n'est pas définie sur "nettoyage automatique" (quelle que soit la manière dont le DBA sous-jacent le fait), la connexion restera ouverte et empêchera un verrou complet sur la table de passer. Cela m'est arrivé dans un morceau de code qui vérifiait si une ligne existait en sélectionnant cette ligne et en vérifiant si elle avait une erreur (n'existe pas) ou non (elle doit exister), mais sans réellement récupérer la ligne .

Demandez à la base de données

Une autre façon d'obtenir le coupable si vous avez un MySQL récent, mais pas trop récent puisque cela va être obsolète , est (vous avez à nouveau besoin de privilèges sur le schéma d'information)

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
     WHERE LOCK_TRX_ID IN 
        (SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);

Solution réelle, nécessitant du temps et du travail

Le problème est généralement causé par cette architecture :

Lorsque l'application Web meurt ou que l'instance de thread léger de l'application Web meurt, le pool de conteneurs/connexions peut ne pas . Et c'est le conteneur qui maintient la connexion ouverte, donc évidemment la connexion ne se ferme pas. Comme on pouvait s'y attendre, MySQL ne considère pas l'opération comme terminée .

Si l'application Web ne s'est pas nettoyée après elle-même (pas de ROLLBACK ou COMMIT pour une transaction, pas de UNLOCK TABLES , etc.), alors tout ce que cette application Web a commencé à faire existe toujours , et bloque peut-être encore tout le monde.

Il y a alors deux solutions. Le pire est de réduire le délai d'inactivité . Mais devinez ce qui se passe si vous attendez trop longtemps entre deux requêtes (exactement :"le serveur MySQL est parti"). Vous pouvez alors utiliser mysql_ping si disponible (bientôt obsolète. Il existe des solutions de contournement pour l'AOP. Ou vous pourriez vérifier que erreur et rouvrez la connexion si cela se produit (c'est la méthode Python). Donc - pour une petite commission de performance - c'est faisable.

La meilleure solution, la plus intelligente, est moins simple à mettre en œuvre. Efforcez-vous de nettoyer le script après lui-même, en vous assurant de récupérer toutes les lignes ou de libérer toutes les ressources de requête, d'attraper toutes les exceptions et de les traiter correctement, ou, si possible, ignorer complètement les connexions persistantes . Laissez chaque instance créer sa propre connexion ou utiliser un smart chauffeur de piscine (en PHP PDO, utilisez PDO::ATTR_PERSISTENT explicitement défini sur false ). Alternativement (par exemple en PHP), vous pouvez faire en sorte que les gestionnaires de destruction et d'exception forcent le nettoyage de la connexion en validant ou en annulant les transactions et en émettant des déverrouillages de table explicites.

Je ne connais pas de moyen d'interroger les ressources existantes de l'ensemble de résultats afin de les libérer ; le seul moyen serait de sauver ces ressources dans un tableau privé.