ERROR 1205: Lock wait timeout
El error 1205 aparece cuando una sentencia lleva demasiado tiempo esperando a que otra transacción libere un bloqueo sobre las filas que necesita. InnoDB, el motor de almacenamiento que MySQL usa por defecto, bloquea las filas que una transacción modifica hasta que esa transacción termina con COMMIT o ROLLBACK. Si otra sesión intenta modificar esas mismas filas, se queda esperando, y cuando la espera supera un límite de tiempo, MySQL la cancela y devuelve este error.
Es un error muy común en aplicaciones con varios usuarios o procesos trabajando a la vez sobre las mismas tablas, y suele desconcertar porque la consulta que falla casi nunca es la culpable: simplemente es la que tuvo la mala suerte de llegar después. El verdadero problema está en otra transacción que mantiene los bloqueos durante demasiado tiempo. En este artículo verás cómo leer el mensaje, cómo reproducir el error, cuáles son sus causas habituales, cómo localizar la transacción que bloquea y qué hacer para que no vuelva a ocurrir.
El mensaje de error
El mensaje es siempre el mismo, sin datos sobre la tabla ni la fila implicada, lo que obliga a investigar por otras vías:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
La frase "Lock wait timeout exceeded" significa que se ha superado el tiempo máximo de espera por un bloqueo. Ese tiempo lo controla la variable innodb_lock_wait_timeout, que por defecto vale 50 segundos. Por eso este error suele aparecer después de que la aplicación se haya quedado "colgada" casi un minuto: la sentencia no estaba trabajando, estaba esperando.
La segunda parte, "try restarting transaction", es una sugerencia: MySQL te propone volver a intentar la transacción. A veces basta con eso, porque el bloqueo ya se ha liberado, pero si el origen del problema sigue ahí, el reintento volverá a fallar.
Hay un detalle importante que mucha gente desconoce. Por defecto, cuando se produce este error, MySQL solo revierte la sentencia que ha fallado, no la transacción completa. La transacción sigue abierta, con todos los cambios anteriores pendientes y sus bloqueos todavía activos. Este comportamiento lo controla la variable innodb_rollback_on_timeout, que por defecto está desactivada (OFF). La consecuencia práctica es que la aplicación debe ejecutar un ROLLBACK explícito al capturar el error; si no lo hace, puede terminar confirmando una transacción a medias o dejando bloqueos colgados que provoquen nuevos errores 1205 en otras sesiones.
Conviene no confundir este error con el ERROR 1213 ("Deadlock found when trying to get lock"). En un deadlock, dos transacciones se esperan mutuamente y ninguna podría avanzar nunca, así que InnoDB lo detecta de inmediato y revierte una de ellas completa. En el 1205 no hay un ciclo: una transacción espera a otra que simplemente tarda demasiado en terminar. El 1213 tendrá su propio artículo en esta sección.
Cómo reproducir el error
La forma más clara de entender el 1205 es provocarlo a propósito con dos sesiones abiertas a la vez, por ejemplo en dos terminales conectadas a la misma base de datos. Partimos de una tabla de productos con su stock:
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(20) NOT NULL UNIQUE,
nombre VARCHAR(100) NOT NULL,
stock INT NOT NULL
);
INSERT INTO productos (sku, nombre, stock) VALUES
('TEC-001', 'Teclado mecánico', 25),
('RAT-002', 'Ratón inalámbrico', 40);En la primera sesión abrimos una transacción, modificamos una fila y no la confirmamos:
-- Sesión A
START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- No ejecutamos COMMIT: la fila id = 1 queda bloqueadaEn la segunda sesión intentamos modificar la misma fila. La sentencia no devuelve nada durante 50 segundos y después falla:
-- Sesión B
UPDATE productos SET stock = stock + 10 WHERE id = 1;ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Si en la sesión A ejecutas COMMIT o ROLLBACK mientras la sesión B está esperando, el bloqueo se libera y la sentencia de B se ejecuta en ese mismo instante sin ningún error. Este experimento resume todo el problema: la sesión B no hace nada mal, el problema es que la sesión A retiene el bloqueo.
Causas más frecuentes
Casi todos los errores 1205 se deben a que alguna transacción mantiene bloqueos durante más tiempo del necesario. Estas son las situaciones que lo provocan con más frecuencia.
Una transacción abierta y olvidada
Es la causa más habitual y la más fácil de pasar por alto. Alguien ejecuta un UPDATE o un DELETE desde un cliente gráfico que trabaja con autocommit desactivado, o desde una consola donde escribió START TRANSACTION, y luego se olvida de confirmar. La transacción queda abierta durante horas, con sus filas bloqueadas, hasta que la sesión se cierra.
Algunos clientes gráficos ofrecen el modo de transacciones manuales y lo recuerdan entre sesiones, de modo que cada cambio que hagas queda pendiente hasta que pulses el botón de confirmar. Lo mismo ocurre si alguien ejecuta SET autocommit = 0 en una conexión: a partir de ese momento cada sentencia abre implícitamente una transacción que no termina hasta el siguiente COMMIT. Las aplicaciones con pools de conexiones también pueden dejar transacciones abiertas cuando un error interrumpe el código antes de llegar al COMMIT o al ROLLBACK.
Transacciones demasiado largas
Aunque la transacción termine correctamente, si dura mucho tiempo sus bloqueos también duran mucho. Es típico en procesos por lotes que actualizan miles de filas dentro de una sola transacción, o en código que abre una transacción, modifica una fila y después, antes de confirmar, llama a una API externa, envía un correo o genera un PDF. Mientras esa operación lenta se completa, las filas modificadas siguen bloqueadas para el resto de usuarios.
UPDATE o DELETE sin un índice adecuado
InnoDB no bloquea "las filas que cumplen la condición", sino las filas que recorre para encontrarlas. Si la columna del WHERE no tiene índice, MySQL tiene que leer la tabla entera y, en el nivel de aislamiento por defecto (REPEATABLE READ), bloquea todas las filas que examina. Un UPDATE pensado para tocar una sola fila acaba bloqueando la tabla completa:
-- Sin índice en la columna nombre, esto bloquea todas las filas recorridas
UPDATE productos SET stock = 0 WHERE nombre = 'Ratón inalámbrico';Mientras esa transacción siga abierta, cualquier otra sesión que intente modificar cualquier producto quedará esperando. Puedes comprobar si una consulta usa un índice con EXPLAIN, que te mostrará type: ALL cuando recorre la tabla completa.
Gap locks en REPEATABLE READ
En el nivel de aislamiento REPEATABLE READ, InnoDB no solo bloquea filas existentes, sino también los huecos entre ellas cuando una consulta trabaja con rangos. Por ejemplo, un SELECT ... FOR UPDATE o un UPDATE con WHERE precio BETWEEN 10 AND 50 bloquea ese rango para impedir que otras transacciones inserten filas nuevas dentro de él. El resultado es que un INSERT aparentemente inocente puede quedarse esperando y terminar con el error 1205. Si este es tu caso, conviene revisar los niveles de aislamiento, porque en READ COMMITTED los bloqueos de huecos se usan mucho menos.
Bloqueos de metadatos al modificar tablas
Existe una variante que no tiene que ver con filas. Un ALTER TABLE, un TRUNCATE o un DROP TABLE necesitan un bloqueo de metadatos exclusivo sobre la tabla, y no pueden obtenerlo mientras haya transacciones abiertas que la hayan usado, aunque solo hayan leído de ella. En SHOW PROCESSLIST verás estas sesiones en el estado "Waiting for table metadata lock". Este tiempo de espera lo controla otra variable, lock_wait_timeout, con un valor por defecto de un año, pero si se supera también se devuelve el error 1205.
Cómo encontrar la transacción que bloquea
Como el mensaje no dice quién tiene el bloqueo, el diagnóstico consiste en buscar la transacción culpable. MySQL 8 ofrece varias herramientas para ello, y lo ideal es consultarlas mientras el problema está ocurriendo.
Transacciones activas en INNODB_TRX
La tabla information_schema.INNODB_TRX muestra todas las transacciones de InnoDB abiertas en ese momento. Ordenarlas por antigüedad suele señalar directamente a la culpable:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id,
trx_rows_locked, trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;Una transacción en estado RUNNING que empezó hace varios minutos y cuya columna trx_query está vacía es la señal clásica de una transacción olvidada: ya ejecutó sus sentencias y está esperando un COMMIT que nunca llega. La columna trx_mysql_thread_id es el identificador de la conexión, el mismo que aparece en la columna Id de SHOW PROCESSLIST.
Quién espera a quién con sys.innodb_lock_waits
La vista sys.innodb_lock_waits cruza la información de bloqueos y te dice directamente qué sesión espera y cuál bloquea, junto con las consultas de ambas:
SELECT wait_age, locked_table, waiting_pid, waiting_query,
blocking_pid, blocking_query, sql_kill_blocking_connection
FROM sys.innodb_lock_waits;La columna sql_kill_blocking_connection incluso te propone la sentencia KILL necesaria para cortar la conexión que bloquea. Si quieres el detalle a bajo nivel, las tablas performance_schema.data_locks y performance_schema.data_lock_waits contienen cada bloqueo concreto, con la tabla, el índice y el tipo de bloqueo.
SHOW ENGINE INNODB STATUS
El comando clásico sigue siendo útil, sobre todo en servidores donde no tienes acceso al esquema sys:
SHOW ENGINE INNODB STATUS\GEn la sección TRANSACTIONS aparece cada transacción activa con su antigüedad en segundos y, para las que están esperando, el texto "LOCK WAIT" junto con la tabla y el índice del bloqueo que no consiguen obtener.
Soluciones paso a paso
Una vez identificado el origen, la solución depende de si necesitas desbloquear la situación ahora mismo o evitar que se repita.
Liberar el bloqueo de inmediato
Si la transacción bloqueante está en una sesión que controlas, basta con ir a ella y ejecutar COMMIT o ROLLBACK, según quieras conservar o descartar sus cambios. Si pertenece a una conexión abandonada, puedes cortarla con KILL usando su identificador de proceso:
KILL 1873;Al terminar la conexión, MySQL revierte automáticamente su transacción y libera todos sus bloqueos. Ten en cuenta que ese ROLLBACK descarta los cambios pendientes de esa sesión, y que si la transacción había modificado muchas filas, la reversión puede tardar un tiempo.
Hacer ROLLBACK al capturar el error
En la aplicación, el error 1205 debe tratarse siempre de la misma forma: revertir la transacción completa y, si tiene sentido, reintentarla desde el principio. Como por defecto solo se revierte la sentencia que falló, olvidar el ROLLBACK deja la transacción abierta con sus bloqueos. El artículo sobre COMMIT y ROLLBACK explica cómo estructurar correctamente una transacción.
START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- Si esta sentencia devuelve el error 1205:
ROLLBACK;
-- y después se repite la transacción completaUn reintento automático con uno o dos intentos y una pequeña pausa entre ellos resuelve los bloqueos puntuales causados por picos de actividad. Si los reintentos fallan de forma sistemática, el problema es estructural y hay que buscar la transacción que bloquea.
Añadir índices a las columnas del WHERE
Cuando el diagnóstico muestra transacciones con muchísimas filas bloqueadas (trx_rows_locked muy alto) para modificar solo unas pocas, la solución es crear un índice en la columna que usan los UPDATE y DELETE. Con el índice, InnoDB localiza directamente las filas afectadas y solo bloquea esas:
CREATE INDEX idx_productos_nombre ON productos (nombre);Puedes ver más detalles sobre cómo crear índices en el artículo de CREATE INDEX.
Acortar las transacciones y procesar por lotes
Los procesos que modifican grandes volúmenes de datos deberían dividir el trabajo en lotes pequeños, confirmando cada uno por separado. En lugar de actualizar cien mil filas en una sola transacción, es preferible actualizar mil cada vez:
UPDATE pedidos
SET estado = 'archivado'
WHERE estado = 'entregado' AND fecha < '2025-01-01'
LIMIT 1000;Repitiendo esa sentencia hasta que no afecte a ninguna fila, cada lote mantiene sus bloqueos solo unos milisegundos. Del mismo modo, cualquier operación lenta que no sea de base de datos, como llamadas a servicios externos, envío de correos o cálculos pesados, debe hacerse antes de abrir la transacción o después de confirmarla, nunca en medio.
Ajustar innodb_lock_wait_timeout con criterio
Aumentar el tiempo de espera no soluciona nada: solo hace que el error tarde más en aparecer y que los usuarios esperen más. Lo razonable suele ser lo contrario en operaciones interactivas: reducir el tiempo para que la aplicación falle rápido y pueda reintentar o informar al usuario. La variable se puede cambiar solo para la sesión actual, como cualquier otra variable de sesión:
SET SESSION innodb_lock_wait_timeout = 10;En MySQL 8 también puedes evitar la espera por completo en consultas concretas con SELECT ... FOR UPDATE NOWAIT, que falla al instante si la fila está bloqueada, o con SELECT ... FOR UPDATE SKIP LOCKED, que simplemente salta las filas bloqueadas. Esta última opción es muy útil para colas de trabajos, donde varios procesos compiten por la siguiente tarea pendiente.
Cómo evitar el error
La regla de oro es que las transacciones sean tan cortas como sea posible: abrirlas justo antes de las sentencias que necesitan ser atómicas, confirmarlas justo después y no hacer nada lento entre medias. Asegúrate de que tu código siempre termina cada transacción, tanto en el camino feliz como cuando se produce una excepción, lo que en la mayoría de lenguajes significa un bloque try con ROLLBACK en la gestión de errores.
Revisa también que las columnas usadas en los WHERE de tus UPDATE y DELETE estén indexadas, desconfía de los clientes gráficos con transacciones manuales cuando trabajes contra producción y vigila de vez en cuando information_schema.INNODB_TRX en busca de transacciones con varios minutos de antigüedad. Detectar una transacción olvidada antes de que bloquee a otros usuarios es mucho más cómodo que investigar el error después.
Resumen
El ERROR 1205 Lock wait timeout exceeded indica que una sentencia esperó más de innodb_lock_wait_timeout segundos (50 por defecto) por un bloqueo que tenía otra transacción. La consulta que falla no suele ser la culpable: el problema está en una transacción que retiene bloqueos demasiado tiempo, ya sea porque se quedó abierta, porque es demasiado larga o porque bloquea más filas de las necesarias por falta de índices. Para diagnosticarlo, consulta information_schema.INNODB_TRX y sys.innodb_lock_waits mientras ocurre, y corta la conexión bloqueante con KILL si hace falta. En la aplicación, haz siempre ROLLBACK al capturar el error, porque por defecto la transacción sigue abierta, y reintenta la operación completa. Si el problema es que dos transacciones se bloquean mutuamente, el error que verás será el 1213 de deadlock, no el 1205.
Escrito por Eduardo Lázaro
