Timeouts de SQL Server: cancelación y reintentos seguros
Diferencia límites de conexión, comando y bloqueo, y diseña reintentos que no dupliquen escrituras cuando se pierde una respuesta.
Un timeout indica que el cliente agotó su presupuesto de espera. Por sí solo no revela si SQL era lento, estaba bloqueado, se canceló antes del commit o confirmó la operación antes de perderse la respuesta. Reintentar suponiendo siempre un fallo puede convertir un problema de latencia en operaciones de negocio duplicadas.
Identificar qué límite venció
El timeout de conexión corresponde a abrirla, con posibles problemas de red, autenticación o recursos. El timeout de comando corresponde a ejecutar una instrucción mediante el proveedor cliente. SET LOCK_TIMEOUT limita la espera por bloqueos dentro de SQL Server. Son fases distintas y los valores no son intercambiables.
El ejemplo modifica únicamente el presupuesto de espera por bloqueos de la sesión y después restaura el valor ilimitado. No establece una duración máxima para toda consulta.
SET LOCK_TIMEOUT 1500;
SELECT @@LOCK_TIMEOUT AS LockTimeoutMilliseconds;
SET LOCK_TIMEOUT -1;
SELECT XACT_STATE() AS TransactionState,
@@TRANCOUNT AS TransactionCount;
El timeout de bloqueo genera un error SQL Server clasificable. El timeout de comando suele comunicarlo el controlador y desencadena una solicitud de cancelación. Registra proveedor, detalles del error, duración, identificador de correlación y presencia de una transacción activa. El mensaje genérico del framework web no basta para diagnosticar.
Separa también el plazo HTTP del presupuesto de la base de datos. Si la capa HTTP abandona pero no propaga la cancelación, el trabajo SQL puede continuar. Coordina los límites dejando tiempo para limpiar recursos y devolver una respuesta útil.
Capturar el estado antes de que desaparezca
Durante el incidente, observa esperas, bloqueadores, duración y transacciones abiertas. Estas consultas de lectura necesitan permisos de diagnóstico adecuados a la versión.
SELECT
session_id, request_id, status, command,
wait_type, wait_time, blocking_session_id,
total_elapsed_time, cpu_time,
reads, logical_reads, writes
FROM sys.dm_exec_requests
WHERE session_id <> @@SPID;
SELECT
session_id, status, open_transaction_count,
last_request_start_time, last_request_end_time,
host_name, program_name
FROM sys.dm_exec_sessions
WHERE is_user_process = 1
AND open_transaction_count > 0;
Una solicitud esperando un bloqueo requiere una investigación distinta de otra que consume CPU o espera memoria. La segunda consulta detecta sesiones inactivas con transacciones abiertas, que pueden faltar en la lista de solicitudes activas. Los nombres de equipo y programa son etiquetas suministradas por el cliente, no identidades de seguridad fiables.
En incidentes repetidos, correlaciona horas de la aplicación con una captura dirigida de Extended Events que incluya attention y eventos relevantes de finalización o error. Attention indica que el cliente pidió detener el trabajo. No demuestra si fue por cancelación manual, vencimiento del plazo o problema del cliente.
Cancelar no garantiza que todas las transacciones explícitas hayan sido revertidas. La aplicación debe controlar las transacciones que posee. Tras un fallo, revierte la suya cuando sea posible, gestiona errores de limpieza y descarta una conexión cuyo estado utilizable no puedas establecer. No dependas solo de CATCH en SQL, porque attention no se trata como cualquier error T-SQL.
Tampoco reviertas automáticamente una transacción de un llamador externo sin un contrato definido. El componente debe saber quién inicia, confirma y cancela la operación. Esa frontera es tan importante como el número de segundos configurado.
Reintentar con una identidad estable
Imagina una instrucción parecida a un pago que confirmó correctamente antes de perderse la respuesta. Repetirla con una identidad nueva puede insertarla dos veces. Asigna a cada comando de negocio una clave de idempotencia estable y exige unicidad en la base. Guarda suficiente información para devolver el resultado existente si vuelve la misma clave.
El registro de deduplicación y el cambio de negocio deben confirmar juntos. Guardar la clave primero y ejecutar después en otra transacción permite estados incompletos. Si llega la misma clave con contenido diferente, rechaza la discrepancia.
Usa intentos limitados y espera progresiva solo para fallos que el contrato considere recuperables. Multiplicar solicitudes no arregla una cadena larga de bloqueo. Un estado de commit desconocido exige reconciliar el resultado. Prueba cancelación durante ejecución, durante bloqueo y pérdida de respuesta después del commit.
Aumentar el límite puede ser razonable para un export deliberadamente largo, pero mide la ocupación de recursos y protege las solicitudes interactivas. El objetivo es un plazo claro y un resultado transaccional conocido, no aplazar el siguiente error.
Referencias técnicas: Microsoft Learn: Query timeout troubleshooting · Microsoft Learn: SET LOCK_TIMEOUT · Microsoft Learn: XACT_STATE.