sql >> Base de Datos >  >> RDS >> Mysql

MySQL:Obtener permanentemente Esperando el bloqueo de metadatos de la tabla

La solución aceptada es, lamentablemente, incorrecta . Tiene razón hasta donde dice,

De hecho, esto es (casi seguramente; ver más abajo) qué hacer. Pero luego sugiere,

...y 1398 no la conexión con la cerradura. ¿Como puede ser? 1398 es la conexión esperando para la cerradura Esto significa que todavía no tiene la cerradura, y por lo tanto, matarlo no sirve de nada. El proceso que mantiene el bloqueo seguirá manteniendo el bloqueo, y el siguiente por lo tanto, el subproceso que intenta hacer algo también deténgase e ingrese "Esperando el bloqueo de metadatos" en el debido orden.

No tiene garantía de que los procesos "en espera de bloqueo de metadatos" (WFML) no se bloquearán también, pero puede estar seguro de que eliminar solo los procesos WFML no logrará exactamente nada. .

La verdadera causa es que otro proceso está bloqueando y, lo que es más importante, SHOW FULL PROCESSLIST no le dirá directamente cuál es .

SERA decirle si el proceso está haciendo algo, si. Por lo general, funciona. Aquí, el proceso que mantiene el candado no hace nada , y se esconde entre otros hilos que tampoco hacen nada.

En este caso, el culpable es casi seguro proceso 1396 , que comenzó antes del proceso 1398 y ahora está en Sleep estado, y ha sido durante 46 segundos. Dado que 1396 claramente hizo todo lo que tenía que hacer (como lo demuestra el hecho de que ahora está durmiendo y lo ha hecho durante 46 segundos, en lo que respecta a MySQL ), ningún subproceso que se haya ido a dormir antes podría haber mantenido un bloqueo (o 1396 también se habría estancado).

IMPORTANTE :si se conectó a MySQL como usuario limitado, SHOW FULL PROCESSLIST no Mostrar todos los procesos. Por lo tanto, el bloqueo puede estar retenido por un proceso que no ve.

Mejor 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

Lo anterior se puede ajustar para mostrar solo los procesos en estado SLEEP, y de todos modos los ordenará por tiempo descendente, por lo que es más fácil encontrar el proceso que está colgando (generalmente es el Sleep 'ing uno inmediatamente antes de los que "esperan el bloqueo de metadatos").

Lo importante

Deje cualquier proceso de "espera de bloqueo de metadatos" solo .

Solución rápida y sucia, no muy recomendable pero rápida

Mata a todos procesos en estado de "suspensión", en la misma base de datos, que son más antiguos que el más antiguo subproceso en estado "esperando bloqueo de metadatos". Esto es lo que Arnaud Amaury habría hecho:

  • para cada base de datos que tenga al menos un subproceso en WaitingForMetadataLock:
    • la conexión más antigua en WFML en esa base de datos resulta tener Z segundos de antigüedad
    • TODOS los subprocesos "Dormir" en esa base de datos y anteriores a Z deben desaparecer. Comience con los más frescos, por si acaso.
    • Si existe una conexión más antigua y que no está inactiva en esa base de datos, entonces tal vez esa sea la que mantiene el bloqueo, pero está haciendo algo . Por supuesto, puede matarlo, pero especialmente si es una ACTUALIZACIÓN/INSERCIÓN/ELIMINACIÓN, lo hace bajo su propio riesgo.

Noventa y nueve veces de cada cien, el subproceso a eliminar es el más joven entre aquellos en estado de suspensión que son mayores que el anterior esperando el bloqueo de metadatos:

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

(*) el orden TIME en realidad tiene milisegundos, o eso me dijeron, simplemente no los muestra. Entonces, aunque ambos procesos tienen un valor de tiempo de 19, el más bajo debería ser más joven.

Corrección más enfocada

Ejecute SHOW ENGINE INNODB STATUS y mira la sección "TRANSACCIÓN". Encontrarás, entre otros, algo como

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;

Ahora verifica con SHOW FULL PROCESSLIST ¿Qué está haciendo el ID de subproceso 1396 con su transacción #1701? Lo más probable es que esté en estado de "Reposo". Entonces:una transacción activa (# 1701) con un bloqueo activo, incluso ha realizado algunos cambios, ya que tiene una entrada de registro de deshacer ... pero actualmente está inactiva. Eso y no otro es el hilo que necesitas matar. Perdiendo esos cambios.

Recuerde que no hacer nada en MySQL no significa no hacer nada en general. Si obtiene algunos registros de MySQL y crea un CSV para la carga FTP, durante la carga FTP la conexión MySQL está inactiva.

En realidad, si el proceso que usa MySQL y el servidor MySQL están en la misma máquina, esa máquina ejecuta Linux y usted tiene privilegios de root, hay una manera de averiguar qué proceso tiene la conexión que solicitó el bloqueo. Esto, a su vez, permite determinar (a partir del uso de la CPU o, en el peor de los casos, strace -ff -p pid ) si ese proceso es realmente haciendo algo o no, para ayudar a decidir si es seguro matar.

¿Por qué sucede esto?

Veo que esto sucede con las aplicaciones web que usan conexiones MySQL "persistentes" o "agrupadas", que hoy en día generalmente ahorran muy poco tiempo:la instancia de la aplicación web terminó, pero la conexión no , por lo que su bloqueo sigue vivo... y bloqueando a todos los demás.

Otra forma interesante que encontré es, en las hipótesis anteriores, ejecutar una consulta que devuelve algunas filas, y solo recuperar algunas de ellas . Si la consulta no está configurada para "limpieza automática" (sin embargo, el DBA subyacente lo hace), mantendrá la conexión abierta y evitará que se produzca un bloqueo completo en la tabla. Me sucedió esto en un fragmento de código que verificaba si existía una fila seleccionando esa fila y verificando si tenía un error (no existe) o no (debe existir), pero sin recuperar realmente la fila .

Pregunte al DB

Otra forma de encontrar al culpable si tiene un MySQL reciente, pero no demasiado reciente ya que esto va a ser obsoleto , es (necesita privilegios nuevamente en el esquema de información)

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

Solución real, requiere tiempo y trabajo

El problema suele estar causado por esta arquitectura:

Cuando la aplicación web muere, o la instancia de subproceso ligero de la aplicación web muere, el contenedor/grupo de conexiones podría no hacerlo . Y es el contenedor eso mantiene la conexión abierta, por lo que obviamente la conexión no se cierra. Como era de esperar, MySQL no considera la operación completa .

Si la aplicación web no se limpió después de sí misma (sin ROLLBACK o COMMIT para una transacción, no UNLOCK TABLES , etc.), entonces cualquier cosa que esa aplicación web haya comenzado a hacer sigue existiendo y aún podría estar bloqueando a todos los demás.

Hay entonces dos soluciones. La peor es reducir el tiempo de inactividad . Pero adivine qué sucede si espera demasiado entre dos consultas (exactamente:"El servidor MySQL se ha ido"). Luego podría usar mysql_ping si está disponible (pronto quedará obsoleto. Hay soluciones alternativas para DOP. O podrías comprobar eso error, y vuelva a abrir la conexión si sucede (esta es la forma de Python). Entonces, por una pequeña tarifa de rendimiento, es factible.

La solución mejor y más inteligente es menos sencilla de implementar. Esfuércese por tener el script limpio después de sí mismo, asegurándose de recuperar todas las filas o liberar todos los recursos de consulta, detectar todas las excepciones y tratarlas correctamente o, si es posible, omitir las conexiones persistentes por completo . Deje que cada instancia cree su propia conexión o utilice una conexión inteligente conductor de la piscina (en PHP PDO, use PDO::ATTR_PERSISTENT establecido explícitamente en false ). Alternativamente (por ejemplo, en PHP) puede hacer que los controladores de destrucción y excepción fuercen la limpieza de la conexión confirmando o revirtiendo transacciones y emitiendo desbloqueos de tabla explícitos.

No conozco una forma de consultar los recursos del conjunto de resultados existentes para liberarlos; la única forma sería salvar esos recursos en una matriz privada.