Error SQL · transacciones y concurrencia

Deadlock en SQL: dos transacciones se bloquean entre sí

Un deadlock o interbloqueo aparece cuando dos o más transacciones forman un ciclo: cada una conserva un bloqueo que otra necesita y ninguna puede avanzar. PostgreSQL, MySQL/InnoDB y SQL Server detectan ese ciclo y abortan una de las transacciones para romperlo; SQLite suele mostrar conflictos de concurrencia con SQLITE_BUSY o SQLITE_LOCKED bajo su modelo de escritor único.

Lectura: 15–20 minTransaccionesConcurrencia

El síntoma: una transacción se revierte aunque el SQL sea válido

El SQL puede ser correcto y aun así fallar cuando dos sesiones modifican recursos en un orden incompatible. El mensaje cambia por motor, pero la señal importante es la misma: una transacción fue elegida como víctima o no pudo continuar porque existía un ciclo de espera.

  • PostgreSQL: SQLSTATE 40P01, deadlock_detected.
  • MySQL/InnoDB: error 1213; InnoDB revierte una transacción para romper el interbloqueo.
  • SQL Server: error 1205; el motor elige una víctima de deadlock y revierte su transacción.
  • SQLite: no usa el mismo patrón de víctima de deadlock de estos servidores en su modo habitual; al serializar escrituras, los conflictos entre conexiones suelen aparecer como SQLITE_BUSY (“database is locked”) o SQLITE_LOCKED.

La causa real: un ciclo de bloqueos

Esperar un bloqueo no basta para hablar de deadlock. Si la transacción B espera a A y A todavía puede terminar, existe una espera normal. Hay interbloqueo cuando la dependencia vuelve al punto de partida: A espera algo de B mientras B espera algo de A.

Dos sesiones, orden opuesto
-- Sesión A
BEGIN;
UPDATE pedidos SET estado = 'procesando' WHERE id = 1;
-- conserva un bloqueo sobre el pedido 1
UPDATE pedidos SET estado = 'procesando' WHERE id = 2;

-- Sesión B, al mismo tiempo
BEGIN;
UPDATE pedidos SET estado = 'procesando' WHERE id = 2;
-- conserva un bloqueo sobre el pedido 2
UPDATE pedidos SET estado = 'procesando' WHERE id = 1;

Si A ya bloqueó la fila 1 y B ya bloqueó la fila 2, A puede quedar esperando la 2 mientras B espera la 1. Un motor con detección de deadlocks rompe el ciclo abortando una de las transacciones. Por eso no debes asumir cuál de las dos “ganará”.

Idea clave: el problema no es que exista concurrencia, sino que distintas transacciones adquieren recursos compartidos en un orden incompatible.

Diagnóstico: confirma el ciclo antes de cambiar consultas al azar

  1. Identifica qué transacción fue revertida y conserva el código de error del motor.
  2. Recupera las sentencias de las transacciones implicadas, no solo la última que falló.
  3. Anota qué filas, tablas o rangos bloquea cada operación y en qué orden.
  4. Busca el ciclo: A posee X y espera Y; B posee Y y espera X.
  5. Comprueba si todas las rutas de la aplicación acceden a esos recursos con el mismo orden.

En MySQL puedes inspeccionar el último deadlock de InnoDB con SHOW ENGINE INNODB STATUS. SQL Server ofrece información de deadlocks mediante sus mecanismos de diagnóstico; PostgreSQL registra el error de deadlock y su código SQLSTATE. El objetivo de estas herramientas es reconstruir el orden de espera, no “adivinar” qué consulta parece más pesada.

Corrección principal: usa un orden consistente

Si dos flujos deben modificar los mismos recursos, intenta que los adquieran en el mismo orden. En el ejemplo anterior, ambas transacciones deberían procesar primero el pedido 1 y después el 2.

Orden consistente
-- Sesión A
BEGIN;
UPDATE pedidos SET estado = 'procesando' WHERE id = 1;
UPDATE pedidos SET estado = 'procesando' WHERE id = 2;
COMMIT;

-- Sesión B: mismo orden de acceso
BEGIN;
UPDATE pedidos SET estado = 'enviado' WHERE id = 1;
UPDATE pedidos SET estado = 'enviado' WHERE id = 2;
COMMIT;

La segunda sesión puede tener que esperar, pero ya no crea el mismo ciclo de dependencias. También ayuda mantener las transacciones cortas, evitar pausas mientras están abiertas y tocar solo las filas necesarias.

Después del deadlock: reintenta la transacción completa

Cuando el motor aborta una transacción por deadlock, el código de aplicación debe tratar ese fallo como recuperable cuando la operación sea segura de reintentar. No basta con reenviar ciegamente la última sentencia: el estado transaccional puede haber sido revertido y la unidad lógica debe comenzar de nuevo.

  • En PostgreSQL, la documentación recomienda considerar el reintento de fallos 40P01.
  • En MySQL/InnoDB, un deadlock revierte la transacción completa; la documentación indica repetir todas sus operaciones.
  • En SQL Server, el error 1205 revierte la transacción elegida como víctima y la aplicación puede volver a ejecutar la unidad de trabajo.

En aplicaciones reales conviene limitar el número de reintentos y usar una espera breve variable entre intentos para evitar que muchas sesiones vuelvan a competir exactamente al mismo tiempo.

Diferencias entre motores

MotorSeñal habitualQué hace el motorRespuesta recomendada
PostgreSQL40P01 deadlock_detectedDetecta el ciclo y aborta una transacción.Corrige el orden de bloqueos y prepara reintento de la transacción.
MySQL / InnoDBError 1213Detecta el deadlock y revierte una transacción.Repite la transacción completa y reduce colisiones.
SQL ServerError 1205Elige una víctima y revierte su transacción.Reintenta la unidad de trabajo y analiza el gráfico de deadlock.
SQLiteSQLITE_BUSY / SQLITE_LOCKED según el conflictoSerializa escrituras en el modelo habitual; un escritor puede impedir que otro continúe.Mantén transacciones cortas, configura la espera adecuada y no traduzcas automáticamente el problema a un deadlock de servidor.

Cómo reducir deadlocks sin sacrificar integridad

  • Orden estable: toca tablas y filas compartidas siguiendo el mismo criterio en todos los flujos.
  • Transacciones cortas: no dejes una transacción abierta mientras esperas interacción del usuario o haces trabajo ajeno a la base.
  • Filtros e índices adecuados: reducir el conjunto que una sentencia debe examinar puede reducir el tiempo y alcance de los bloqueos.
  • Menos recursos por transacción: evita abarcar más filas o tablas de las que necesita la regla de negocio.
  • Reintento controlado: trata el deadlock como un evento posible en sistemas concurrentes, no como una excepción imposible.

Práctica correctiva: define un orden antes de modificar

Intermedia · transacciones

Prepara una secuencia estable de pedidos

Dos procesos pueden recibir los mismos pedidos en órdenes diferentes. Antes de ejecutar las modificaciones, genera una secuencia ascendente por pedido_id para que ambos procesos puedan aplicar el mismo criterio.

Criterio de éxito: la salida debe ser 1, 3, 4 y 8, en ese orden.

Qué debes recordar

  • Un deadlock es un ciclo de espera, no cualquier bloqueo lento.
  • PostgreSQL, InnoDB y SQL Server rompen el ciclo abortando una transacción; no confíes en cuál será la víctima.
  • La defensa principal es adquirir recursos compartidos en un orden consistente y mantener transacciones cortas.
  • La aplicación debe estar preparada para reintentar una transacción abortada cuando la operación sea idempotente o esté diseñada para reintento seguro.
  • SQLite tiene un modelo de concurrencia distinto: interpreta BUSY/LOCKED según su propio sistema de bloqueo.

Conceptos relacionados

Fuentes técnicas

La explicación se contrastó con documentación primaria de cada motor: