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”) oSQLITE_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.
-- 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á”.
Diagnóstico: confirma el ciclo antes de cambiar consultas al azar
- Identifica qué transacción fue revertida y conserva el código de error del motor.
- Recupera las sentencias de las transacciones implicadas, no solo la última que falló.
- Anota qué filas, tablas o rangos bloquea cada operación y en qué orden.
- Busca el ciclo: A posee X y espera Y; B posee Y y espera X.
- 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.
-- 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
| Motor | Señal habitual | Qué hace el motor | Respuesta recomendada |
|---|---|---|---|
| PostgreSQL | 40P01 deadlock_detected | Detecta el ciclo y aborta una transacción. | Corrige el orden de bloqueos y prepara reintento de la transacción. |
| MySQL / InnoDB | Error 1213 | Detecta el deadlock y revierte una transacción. | Repite la transacción completa y reduce colisiones. |
| SQL Server | Error 1205 | Elige una víctima y revierte su transacción. | Reintenta la unidad de trabajo y analiza el gráfico de deadlock. |
| SQLite | SQLITE_BUSY / SQLITE_LOCKED según el conflicto | Serializa 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
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.
VALUES.WITH pendientes(pedido_id) AS (
VALUES (8), (3), (1), (4)
)
SELECT pedido_id
FROM pendientes
ORDER BY pedido_id;| pedido_id |
|---|
| 1 |
| 3 |
| 4 |
| 8 |
Por qué funciona: esta consulta no bloquea filas ni reproduce un deadlock; su objetivo es preparar un orden determinista que la aplicación pueda usar para ejecutar sus cambios de forma consistente. El principio es pedagógico: todos los flujos que compiten por los mismos recursos deben compartir una política de 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/LOCKEDsegún su propio sistema de bloqueo.
Conceptos relacionados
Fuentes técnicas
La explicación se contrastó con documentación primaria de cada motor: