Cela découle de cette question connexe , où je voulais savoir comment forcer deux transactions à se produire séquentiellement dans un cas trivial (où les deux ne fonctionnent que sur une seule ligne). J'ai obtenu une réponse - utiliser SELECT ... FOR UPDATE
comme première ligne des deux transactions - mais cela conduit à un problème: si la première transaction n'est jamais validée ou annulée, la deuxième transaction sera bloquée indéfiniment. La innodb_lock_wait_timeout
variable définit le nombre de secondes après lesquelles le client essayant d'effectuer la deuxième transaction se verra dire "Désolé, essayez à nouveau" ... mais pour autant que je sache, il essaiera à nouveau jusqu'au prochain redémarrage du serveur. Donc:
- Il doit sûrement y avoir un moyen de forcer un
ROLLBACK
si une transaction prend une éternité? Dois-je recourir à l'utilisation d'un démon pour tuer de telles transactions, et si oui, à quoi ressemblerait un tel démon? - Si une connexion est interrompue par
wait_timeout
ou en coursinteractive_timeout
de transaction, la transaction est-elle annulée? Existe-t-il un moyen de tester cela à partir de la console?
Clarification : innodb_lock_wait_timeout
définit le nombre de secondes pendant lesquelles une transaction attendra qu'un verrou soit libéré avant d'abandonner; ce que je veux, c'est un moyen de forcer le déverrouillage.
Mise à jour 1 : voici un exemple simple qui montre pourquoi innodb_lock_wait_timeout
n'est pas suffisant pour garantir que la deuxième transaction n'est pas bloquée par la première:
START TRANSACTION;
SELECT SLEEP(55);
COMMIT;
Avec le paramètre par défaut de innodb_lock_wait_timeout = 50
, cette transaction se termine sans erreur après 55 secondes. Et si vous ajoutez un UPDATE
avant la SLEEP
ligne, puis lancez une deuxième transaction à partir d'un autre client qui essaie sur SELECT ... FOR UPDATE
la même ligne, c'est la deuxième transaction qui expire, pas celle qui s'est endormie.
Ce que je recherche, c'est un moyen de forcer la fin du sommeil réparateur de cette transaction.
Mise à jour 2 : En réponse aux préoccupations de hobodave sur la réalité de l'exemple ci-dessus, voici un scénario alternatif: un DBA se connecte à un serveur en direct et s'exécute
START TRANSACTION
SELECT ... FOR UPDATE
où la deuxième ligne verrouille une ligne dans laquelle l'application écrit fréquemment. Ensuite, le DBA est interrompu et s'éloigne, oubliant de mettre fin à la transaction. L'application s'arrête jusqu'à ce que la ligne soit déverrouillée. Je voudrais minimiser le temps de blocage de l'application suite à cette erreur.
ROLLBACK
sur la première transaction si cela prend plus den
secondes pour terminer. Existe-t-il un moyen de le faire?MYSQL
n'a pas de configuration pour empêcher ce scénario. Parce qu'il n'est pas acceptable de bloquer le serveur en raison de l'irresponsabilité des clients. Je n'ai trouvé aucune difficulté à comprendre votre question et elle est si pertinente.Réponses:
Plus de la moitié de ce fil semble être sur la façon de poser des questions sur ServerFault. Je pense que la question est logique et est assez simple: comment annuler automatiquement une transaction bloquée?
Une solution, si vous êtes prêt à supprimer toute la connexion, consiste à définir wait_timeout / interactive_timeout. Voir /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .
la source
Étant donné que votre question est posée ici sur ServerFault, il est logique de supposer que vous recherchez une solution MySQL à un problème MySQL, en particulier dans le domaine des connaissances dans lesquelles un administrateur système et / ou un DBA auraient une expertise. En tant que tel, les éléments suivants section répond à vos questions:
Non, ce ne sera pas le cas. Je pense que vous ne comprenez pas
innodb_lock_wait_timeout
. Il fait exactement ce dont vous avez besoin.Il retournera avec une erreur comme indiqué dans le manuel:
innodb_lock_wait_timeout
quelques secondes.Par défaut, la transaction ne sera pas annulée. Il est de la responsabilité de votre code d'application de décider comment gérer cette erreur, que ce soit une nouvelle tentative ou une annulation.
Si vous souhaitez une restauration automatique, cela est également expliqué dans le manuel:
RE: Vos nombreuses mises à jour et commentaires
Premièrement, vous avez déclaré dans vos commentaires que vous "vouliez" dire que vous vouliez un moyen de temporiser la première transaction qui se bloquait indéfiniment. Cela ne ressort pas de votre question initiale et entre en conflit avec "Si la première transaction n'est jamais validée ou annulée, alors la deuxième transaction sera bloquée indéfiniment".
Néanmoins, je peux également répondre à cette question. Le protocole MySQL n'a pas de "délai d'expiration de requête". Cela signifie que vous ne pouvez pas expirer la première transaction bloquée. Vous devez attendre qu'il soit terminé ou tuer la session. Lorsque la session est interrompue, le serveur annule automatiquement la transaction.
La seule autre alternative serait d'utiliser ou d'écrire une bibliothèque mysql qui utilise des E / S non bloquantes qui permettraient à votre application de tuer le thread / fork faisant la requête après N secondes. L'implémentation et l'utilisation d'une telle bibliothèque dépassent le cadre de ServerFault. C'est une question appropriée pour StackOverflow .
Deuxièmement, vous avez déclaré ce qui suit dans vos commentaires:
Ce n'était pas du tout apparent dans votre question initiale, et ce n'est toujours pas le cas. Cela n'a pu être discerné qu'après avoir partagé cette friandise assez importante dans le commentaire.
Si c'est réellement le problème que vous essayez de résoudre, alors je crains que vous ne l'ayez posé sur le mauvais forum. Vous avez décrit un problème de programmation au niveau de l'application qui nécessite une solution de programmation, que MySQL ne peut pas fournir, et qui sort du cadre de cette communauté. Votre dernière réponse répond à la question "Comment empêcher un programme Ruby de boucler indéfiniment?". Cette question est hors sujet pour cette communauté et doit être posée sur StackOverflow .
la source
innodb_lock_wait_timeout
suggèrent le nom et la documentation de ), ce n'est pas le cas dans la situation que j'ai posée: où le client se bloque ou se bloque avant d'obtenir la modification pour faire unCOMMIT
ouROLLBACK
.COMMIT
ouROLLBACK
message n'est envoyé pour résoudre la première transaction? Hobodave, il serait peut-être utile de présenter votre code de test.Dans une situation similaire, mon équipe a décidé d'utiliser pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Il peut signaler ou tuer des requêtes qui (entre autres) sont en cours d'exécution trop longtemps. Il est alors possible de l'exécuter dans un cron toutes les X minutes.
Le problème était qu'une requête dont le temps d'exécution était inopinément long (par exemple, indéfini) a verrouillé une procédure de sauvegarde qui a tenté de vider les tables avec un verrou en lecture, qui à son tour a verrouillé toutes les autres requêtes sur "l'attente des tables à vider" qui n'ont jamais expiré car ils n'attendent pas de verrou, ce qui a entraîné aucune erreur dans l'application, ce qui a finalement entraîné aucune alerte aux administrateurs et l'arrêt de l'application.
Le correctif était évidemment de corriger la requête initiale, mais afin d'éviter que la situation ne se produise accidentellement à l'avenir, il serait formidable s'il était possible de tuer automatiquement (ou de signaler les transactions / requêtes qui durent plus longtemps que la durée spécifiée, quel auteur de la question semble se poser. C'est faisable avec pt-kill.
la source
Une solution simple sera d'avoir une procédure stockée pour tuer les requêtes qui prennent plus de temps que votre
timeout
temps requis et d'utiliser l'innodb_rollback_on_timeout
option avec elle.la source
Après avoir parcouru Google pendant un certain temps, il semble qu'il n'y ait aucun moyen direct de définir un délai par transaction. Voici la meilleure solution que j'ai pu trouver:
La commande
vous donne un tableau avec toutes les commandes en cours d'exécution et le temps (en secondes) depuis leur démarrage. Lorsqu'un client a cessé de donner des commandes, il est décrit comme donnant une
Sleep
commande, laTime
colonne étant le nombre de secondes écoulées depuis l'exécution de sa dernière commande. Vous pouvez donc entrer manuellement etKILL
tout ce qui a uneTime
valeur sur, disons, 5 secondes si vous êtes sûr qu'aucune requête ne devrait prendre plus de 5 secondes sur votre base de données. Par exemple, si le processus avec l'ID 3 a une valeur de 12 dans saTime
colonne, vous pouvez faireLa documentation de la syntaxe KILL suggère qu'une transaction effectuée par le thread avec cet identifiant serait annulée (même si je n'ai pas testé cela, soyez donc prudent).
Mais comment automatiser cela et tuer tous les scripts d'heures supplémentaires toutes les 5 secondes? Le premier commentaire sur cette même page nous donne un indice, mais le code est en PHP; il semble que nous ne pouvons pas faire un
SELECT
onSHOW PROCESSLIST
depuis le client MySQL. En supposant que nous ayons un démon PHP que nous exécutons toutes les$MAX_TIME
secondes, cela pourrait ressembler à ceci:Notez que cela aurait pour effet secondaire de forcer toutes les connexions client inactives à se reconnecter, pas seulement celles qui se comportent mal.
Si quelqu'un a une meilleure réponse, j'aimerais l'entendre.
Edit: Il existe au moins un risque sérieux d'utilisation
KILL
de cette manière. Supposons que vous utilisez le script ci-dessus pourKILL
chaque connexion inactive pendant 10 secondes. Après qu'une connexion a été inactive pendant 10 secondes, mais avant l'KILL
émission, l'utilisateur dont la connexion est interrompue émet uneSTART TRANSACTION
commande. Ils sont ensuiteKILL
édités. Ils envoient unUPDATE
et puis, réfléchissant à deux fois, émettent unROLLBACK
. Cependant, étant donné que laKILL
transaction a été annulée, la mise à jour a été effectuée immédiatement et ne peut pas être annulée! Voir cet article pour un cas de test.la source
Comme l'a suggéré hobodave dans un commentaire, il est possible dans certains clients (mais pas, apparemment, l'
mysql
utilitaire de ligne de commande) de fixer une limite de temps pour une transaction. Voici une démonstration utilisant ActiveRecord dans Ruby:Dans cet exemple, la transaction expire après 5 secondes et est automatiquement annulée . ( Mise à jour en réponse au commentaire de hobodave: si la base de données met plus de 5 secondes pour répondre, la transaction sera annulée dès qu'elle le fera, pas plus tôt.) Si vous vouliez vous assurer que toutes vos transactions expirent après
n
quelques secondes, vous pouvez créer un wrapper autour d'ActiveRecord. Je suppose que cela s'applique également aux bibliothèques les plus populaires en Java, .NET, Python, etc., mais je ne les ai pas encore testées. (Si c'est le cas, veuillez poster un commentaire sur cette réponse.)Les transactions dans ActiveRecord ont également l'avantage d'être sûres si un
KILL
est émis, contrairement aux transactions effectuées à partir de la ligne de commande. Voir /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .Il ne semble pas possible d'appliquer un temps de transaction maximum côté serveur, sauf via un script comme celui que j'ai publié dans mon autre réponse.
la source
Foo.create
commande était bloquée pendant 5 minutes, le Timeout ne le tuerait pas. Ceci est incompatible avec l'exemple fourni dans votre question. UnSELECT ... FOR UPDATE
pourrait facilement bloquer pendant de longues périodes, comme n'importe quelle autre requête.ActiveRecord::Base#find_by_sql
: gist.github.com/856100 Encore une fois, c'est parce que AR utilise les E / S de blocage.timeout
méthode annulera la transaction, mais pas tant que la base de données MySQL ne répondra pas à une requête. Ainsi, latimeout
méthode convient pour empêcher les transactions longues côté client ou les transactions longues qui consistent en plusieurs requêtes relativement courtes, mais pas pour les transactions longues dans lesquelles une seule requête est le coupable.