Esto surgió de esta pregunta relacionada , donde quería saber cómo forzar que dos transacciones ocurrieran secuencialmente en un caso trivial (donde ambas operan en una sola fila). Obtuve una respuesta, usar SELECT ... FOR UPDATE
como la primera línea de ambas transacciones, pero esto lleva a un problema: si la primera transacción nunca se confirma o revierte, la segunda transacción se bloqueará indefinidamente. La innodb_lock_wait_timeout
variable establece el número de segundos después de los cuales el cliente que intenta hacer la segunda transacción se le dice "Lo siento, inténtelo de nuevo" ... pero hasta donde puedo decir, lo intentarían nuevamente hasta el próximo reinicio del servidor. Entonces:
- ¿Seguramente debe haber una manera de forzar un
ROLLBACK
si una transacción está tomando para siempre? ¿Debo recurrir al uso de un demonio para matar tales transacciones, y si es así, cómo sería ese demonio? - Si la conexión es matado por
wait_timeout
ointeractive_timeout
mediados de transacción, la transacción se deshace? ¿Hay alguna forma de probar esto desde la consola?
Aclaración : innodb_lock_wait_timeout
establece el número de segundos que una transacción esperará a que se libere un bloqueo antes de darse por vencido; lo que quiero es una forma de forzar la liberación de un bloqueo.
Actualización 1 : Aquí hay un ejemplo simple que demuestra por qué innodb_lock_wait_timeout
no es suficiente para garantizar que la segunda transacción no esté bloqueada por la primera:
START TRANSACTION;
SELECT SLEEP(55);
COMMIT;
Con la configuración predeterminada de innodb_lock_wait_timeout = 50
, esta transacción se completa sin errores después de 55 segundos. Y si agrega un UPDATE
antes de la SLEEP
línea, luego inicia una segunda transacción desde otro cliente que intenta en SELECT ... FOR UPDATE
la misma fila, es la segunda transacción que agota el tiempo de espera, no la que se durmió.
Lo que estoy buscando es una forma de forzar el final del sueño reparador de esta transacción.
Actualización 2 : en respuesta a las preocupaciones de hobodave sobre cuán realista es el ejemplo anterior, aquí hay un escenario alternativo: un DBA se conecta a un servidor en vivo y se ejecuta
START TRANSACTION
SELECT ... FOR UPDATE
donde la segunda línea bloquea una fila en la que la aplicación escribe con frecuencia. Luego, el DBA se interrumpe y se va, olvidando finalizar la transacción. La aplicación se detiene hasta que se desbloquea la fila. Me gustaría minimizar el tiempo que la aplicación está atascada como resultado de este error.
ROLLBACK
en la primera transacción si tarda más den
segundos en completarse. ¿Hay alguna manera de hacerlo?MYSQL
tiene una configuración para evitar este escenario. Porque no es aceptable que el servidor se cuelgue debido a la irresponsabilidad de los clientes. No encontré ninguna dificultad para entender su pregunta, también es tan relevante.Respuestas:
Más de la mitad de este hilo parece ser sobre cómo hacer preguntas en ServerFault. Creo que la pregunta tiene sentido y es bastante simple: ¿cómo deshace automáticamente una transacción estancada?
Una solución, si está dispuesto a eliminar toda la conexión, es establecer wait_timeout / interactive_timeout. Ver /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .
fuente
Dado que su pregunta se hace aquí en ServerFault, es lógico suponer que está buscando una solución MySQL para un problema MySQL, particularmente en el ámbito del conocimiento en el que un administrador de sistemas y / o un DBA tendrían experiencia. Como tal, lo siguiente La sección aborda sus preguntas:
No, no lo hará. Creo que no estás entendiendo
innodb_lock_wait_timeout
. Hace exactamente lo que necesitas.Volverá con un error como se indica en el manual:
innodb_lock_wait_timeout
segundos.Por defecto, la transacción no se revertirá. Es responsabilidad de su código de aplicación decidir cómo manejar este error, ya sea que lo intente nuevamente o retroceda.
Si desea una reversión automática, eso también se explica en el manual:
RE: sus numerosas actualizaciones y comentarios
Primero, usted ha declarado en sus comentarios que "quiso" decir que desea una forma de expirar la primera transacción que se está bloqueando indefinidamente. Esto no se desprende de su pregunta original y entra en conflicto con "Si la primera transacción nunca se confirma o revierte, entonces la segunda transacción se bloqueará indefinidamente".
No obstante, también puedo responder esa pregunta. El protocolo MySQL no tiene un "tiempo de espera de consulta". Esto significa que no puede agotar el tiempo de espera de la primera transacción bloqueada. Debe esperar hasta que termine o cerrar la sesión. Cuando finaliza la sesión, el servidor revierte automáticamente la transacción.
La única otra alternativa sería usar o escribir una biblioteca mysql que utilice E / S sin bloqueo que permitiría que su aplicación elimine el hilo / tenedor que realiza la consulta después de N segundos. La implementación y el uso de dicha biblioteca están más allá del alcance de ServerFault. Esta es una pregunta apropiada para StackOverflow .
En segundo lugar, has indicado lo siguiente en tus comentarios:
Esto no fue del todo aparente en su pregunta original, y aún no lo es. Esto solo se pudo discernir después de que compartió este dato bastante importante en el comentario.
Si este es realmente el problema que está tratando de resolver, me temo que lo ha preguntado en el foro equivocado. Usted ha descrito un problema de programación a nivel de aplicación que requiere una solución de programación, una que MySQL no puede proporcionar, y está fuera del alcance de esta comunidad. Su última respuesta responde a la pregunta "¿Cómo evito que un programa Ruby se repita infinitamente?". Esa pregunta está fuera de tema para esta comunidad y debe formularse en StackOverflow .
fuente
innodb_lock_wait_timeout
sugieren el nombre y la documentación ), no es así en la situación que planteé: donde el cliente se cuelga o falla antes de obtener el cambio para hacer unCOMMIT
oROLLBACK
.COMMIT
oROLLBACK
se envía el mensaje a resolver la primera transacción? Hobodave, quizás sería útil si presentaras tu código de prueba.En una situación similar, mi equipo decidió usar pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Puede informar o eliminar consultas que (entre otras cosas) se ejecutan durante demasiado tiempo. Entonces es posible ejecutarlo en un cron cada X minutos.
El problema era que una consulta que tenía un tiempo de ejecución inesperadamente largo (por ejemplo, indefinido) bloqueó un procedimiento de copia de seguridad que trató de eliminar las tablas con el bloqueo de lectura, que a su vez bloqueó todas las demás consultas sobre "esperar a que las tablas se vacíen", que nunca expiraron porque no están esperando un bloqueo, lo que resultó sin errores en la aplicación, lo que finalmente resultó sin alertas a los administradores y la detención de la aplicación.
Obviamente, la solución era solucionar la consulta inicial, pero para evitar que la situación ocurriera accidentalmente en el futuro, sería genial si fuera posible eliminar automáticamente (o informar) transacciones / consultas que duran más de un tiempo específico, qué autor de la pregunta parece estar preguntando. Esto es factible con pt-kill.
fuente
Una solución simple será tener un procedimiento almacenado para eliminar las consultas que toman más tiempo que el
timeout
tiempo requerido y usar lainnodb_rollback_on_timeout
opción junto con él.fuente
Después de buscar en Google por un tiempo, parece que no hay una forma directa de establecer un límite de tiempo por transacción. Aquí está la mejor solución que pude encontrar:
El comando
le proporciona una tabla con todos los comandos que se ejecutan actualmente y el tiempo (en segundos) desde que comenzaron. Cuando un cliente ha dejado de dar comandos, se describe como dar un
Sleep
comando, y laTime
columna es el número de segundos desde que se completó su último comando. Por lo tanto, podría ingresar manualmente yKILL
todo lo que tenga unTime
valor superior a, por ejemplo, 5 segundos si está seguro de que ninguna consulta debería tomar más de 5 segundos en su base de datos. Por ejemplo, si el proceso con id 3 tiene un valor de 12 en suTime
columna, podría hacerLa documentación para la sintaxis KILL sugiere que una transacción que se está llevando a cabo por el subproceso con esa identificación se revertiría (aunque no lo he probado, así que tenga cuidado).
Pero, ¿cómo automatizar esto y eliminar todos los scripts de tiempo extra cada 5 segundos? El primer comentario en esa misma página nos da una pista, pero el código está en PHP; parece que no podemos hacer un
SELECT
encendidoSHOW PROCESSLIST
desde el cliente MySQL. Aún así, suponiendo que tengamos un demonio PHP que ejecutamos cada$MAX_TIME
segundo, podría verse así:Tenga en cuenta que esto tendría el efecto secundario de forzar la reconexión de todas las conexiones inactivas del cliente, no solo aquellas que se comportan mal.
Si alguien tiene una mejor respuesta, me encantaría escucharla.
Editar: hay al menos un riesgo grave de usar
KILL
de esta manera. Suponga que usa el script anterior paraKILL
cada conexión que esté inactiva durante 10 segundos. Después de que una conexión ha estado inactiva durante 10 segundos, pero antes de queKILL
se emita, el usuario cuya conexión se está desconectando emite unSTART TRANSACTION
comando. Luego sonKILL
ed. Envían unUPDATE
y luego, pensando dos veces, emiten unROLLBACK
. Sin embargo, debido a queKILL
revertieron su transacción original, la actualización se realizó de inmediato y no se puede revertir. Vea esta publicación para un caso de prueba.fuente
Como hobodave sugirió en un comentario, es posible en algunos clientes (aunque no, aparentemente, la
mysql
utilidad de línea de comandos) establecer un límite de tiempo para una transacción. Aquí hay una demostración usando ActiveRecord en Ruby:En este ejemplo, la transacción agota el tiempo de espera después de 5 segundos y se revierte automáticamente . ( Actualice en respuesta al comentario de hobodave: si la base de datos tarda más de 5 segundos en responder, la transacción se revertirá tan pronto como lo haga, no antes). Si desea asegurarse de que todas sus transacciones expiren después de
n
segundos, podrías construir un contenedor alrededor de ActiveRecord. Supongo que esto también se aplica a las bibliotecas más populares en Java, .NET, Python, etc., pero aún no las he probado. (Si es así, publique un comentario sobre esta respuesta).Las transacciones en ActiveRecord también tienen la ventaja de ser seguras si
KILL
se emite un, a diferencia de las transacciones realizadas desde la línea de comandos. Ver /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .No parece posible imponer un tiempo máximo de transacción en el lado del servidor, excepto a través de un script como el que publiqué en mi otra respuesta.
fuente
Foo.create
comando se bloqueó durante 5 minutos, el tiempo de espera no lo mataría. Esto es incompatible con el ejemplo proporcionado en su pregunta. ASELECT ... FOR UPDATE
podría bloquearse fácilmente durante largos períodos de tiempo, como cualquier otra consulta.ActiveRecord::Base#find_by_sql
: gist.github.com/856100 Nuevamente, esto se debe a que AR usa el bloqueo de E / S.timeout
método revertirá la transacción, pero no hasta que la base de datos MySQL responda a una consulta. Por lo tanto, eltimeout
método es adecuado para evitar transacciones largas en el lado del cliente o transacciones largas que consisten en varias consultas relativamente cortas, pero no para transacciones largas en las que una sola consulta es la culpable.