Waiting for table metadata lock en MySql

metadata lock en mysql"Waiting for table metadata lock"... Este error nos lo encontramos en MySql al lanzar una consulta del tipo DDL. Se produce normalmente al lanzar una consulta para modificar un objeto que está siendo usado actualmente en nuestra base de datos. Empezaremos a lidiar con él a partir de la versión 5.5 de MySql y a consecuencia de la introducción de un nuevo tipo de bloqueos: "metadata locking".

 

Por que sucede

Mysql 5.5 usa el bloqueo a nivel de metadatos (metadata locking) para controlar el acceso a los diferentes tipos de objetos ya sean tablas, triggers etc. El motor de la base de datos lo usa para asegurar la consistencia pero a la vez supone un posible cambio en la manera de trabajar. La gracia del metadata lock es evitar cambios en la estructura de tablas mientras se esta trabajando con ella en una transacción anterior al cambio. En versiones anteriores esto no tenia lugar porque este bloqueo tenia lugar a nivel de consulta, no de transacción. Durante la transacción de la conexión a, podemos hacer un cambio desde la conexión b que mysql se la come.

Volviendo a Mysql 5.5 vamos a poner una situación de ejemplo para entenderlo. Antes estábamos en un nuevo desarrollo y modificábamos las tablas en el entorno de desarrollo a nuestro gusto para luego trasladar los cambios al entorno de producción "on-the-fly". Ahora, si no queremos problemas, esto ya no nos valdrá por este nuevo tipo de bloqueos. Ese cambio en producción será más tedioso. Aunque sepamos que a nivel lógico no supone un problema trasladar el cambio al entorno de producción, debemos asegurarnos que no hayan transacciones abiertas que impliquen al objeto. Generalmente lo haremos parando la aplicación que pueda usar esa tabla por muy pocos segundos, ya que la alternativa sería lanzar el alter y matar una a una las sesiones anteriores. En el último caso podemos hacer trampa si la tabla involucrada es Innodb y podemos usar SHOW ENGINE InndoDb STATUS para ver transacciones abiertas. Si es myisam no podremos saberlo más que por la antigüedad de la sesión.

Reflexionando un poco y contrariamente a lo que podríamos pensar la primera vez que lo vemos, una sesión que se queda en estado "Waiting for metadata lock" puede usar solo tablas myisam. Este tipo de bloqueos es independiente y no va ligado a tablas innodb, sino a cualquier tipo.
 

Vamos con un ejemplo específico

Para ejemplificarlo de forma sencilla, basta con abrir una conexión Mysql (a), deshabilitamos para esa sesión el autocommit, creamos una tabla y hacemos un select sobre ella. A continuación abrimos una nueva conexión para lanzar un alter table sobre la tabla creada por la primera conexión y ya tenemos el bloqueo.

/*Conexión (a)*/

CREATE TABLE test (ID int) ENGINE=myisam;
SET @@autocommit=0;
SELECT * FROM test;
/*Conexión (b)*/

ALTER TABLE test RENAME TO prueba;
/*Conexión (c) para ver como se queda esperando:*/

mysql> show full processlist;
+----+---+-------+---------------------------------+------------------------------------+
| Id |...| Time  | State                           | Info                               |
+----+---+-------+---------------------------------+------------------------------------+
| 1  |...| 1653  |                                 | NULL                               |
| 2  |...| 0     | NULL                            | show full processlist              |
| 3  |...| 18012 | Waiting for table metadata lock | ALTER TABLE test RENAME TO prueba  |
+----+---+-------+---------------------------------+------------------------------------+

 

Como vemos, tiene lugar el bloqueo aunque sea una tabla myisam. Aunque en esta prueba hemos cambiado a saco el nombre de la tabla, podemos hacer algo menos a saco como añadir nuevos campos. En el anterior, para deshacer el bloqueo y que termine el alter de la segunda tabla basta con cerrar la transacción de la primera conexión.

 

Convivir con ello

Por el momento y que yo sepa, no hay manera directa de saber quién mantiene este tipo de bloqueo sobre un objeto. Entonces no nos queda otra que lidiar con ello y solo podemos hacerlo proveyendo este tipo de situaciones. Según nuestro entorno podemos intentar adaptarnos de la siguiente manera:

  • De forma preventiva, Si no usamos conexiones persistentes contra la base de datos, podemos cambiar el valor de wait_tiemout y ajustarlo al mínimo posible. Wait_timeout  es el número de segundos que el servidor espera para recibir actividad en una conexión no interactiva antes de cerrarla. Me imagino que en un entorno web, esto es fácilmente ajustable a 30-60 segundos, mientras que en otro tipo de aplicaciones no será posible.
  • Detener momentáneamente las aplicaciones que potencialmente usan los objetos implicados. Obvio aunque no siempre es posible hacerlo.
  • Garantizar un tratamiento correcto a nivel de transacciones por nuestras aplicaciones. Si nos encontramos con bloqueos del tipo waiting for metadata lock a menudo, deberíamos hacer un repasillo para conocer como funcionan exactamente las transacciones que usan las tablas implicadas.
  • Chequear las sesiones  abiertas en producción que sean anteriores a la que va a ejecutar el ALTER. En el entorno de desarrollo no importa si nos cuesta y si matamos sesiones de manera indiscriminada. Incluso, podemos reiniciar mysql y ejecutar el alter lo primero antes de que entren nuevas conexiones. En cambio, en producción nos interesa mucho ser más selectivos y cerrar conexiones de forma quirúrgica. La manera más fácil puede ser consultar la tabla information_schema.processlist y buscar las sesiones por id (asignado de forma secuencial por mysql). Por ejemplo así obtenemos los Kill necesarios:
SELECT concat('KILL ',ID, ';') 
FROM INFORMATION_SCHEMA.PROCESSLIST 
WHERE ID < connection_id();

Por último, es interesante saber como se relaciona el bloqueo de metadatos y la query_cache. No había caído en la cuenta pero es posible que el bloqueo no tenga lugar para consultas sql posteriores mientras dure el bloqueo si la consulta está cacheada. Esto esta bien saberlo y nos puede explicar alguna excepción que nos encontremos, pero no creo que lo podemos considerar para beneficiarnos de ello. Comentar que en MariaDb también podemos sufrir metadata locking.

 Mariadb es un reflejo de mysql

En conclusión...

... con esta nueva forma de proceder del motor de MySql incluyendo este nuevo tipo bloqueo, están garantizando realmente la integridad de las transacciones en sus tablas implicadas. Es tedioso tenerlo en cuenta al principio y sorprende cuando lo ves por primera vez, pero de peores hemos salido.