SQL Server en la práctica

Colas SQL Server: reservar, confirmar y recuperar

Reserve trabajo de forma atómica, comprenda READPAST y evite que un trabajador antiguo sobrescriba al nuevo propietario.

Un SELECT seguido de un UPDATE separado no reserva trabajo de forma segura. Dos trabajadores pueden seleccionar la misma fila antes de que cualquiera cambie su estado. La cola necesita una transición atómica y una confirmación breve. El trabajo de negocio potencialmente lento debe ejecutarse después.

Reservar con una instrucción

Cree la tabla una vez en una base desechable. El índice permite buscar tareas listas sin recorrer continuamente el historial completado. El contenido es pequeño a propósito; una cola real puede guardar una referencia a documentos grandes almacenados aparte.

CREATE TABLE dbo.QueueDemo
( JobId bigint IDENTITY PRIMARY KEY, State char(1) NOT NULL,
  ClaimToken uniqueidentifier NULL, LeaseUntil datetime2(3) NULL,
  Payload nvarchar(100) NOT NULL );
CREATE INDEX IX_QueueDemo_Ready ON dbo.QueueDemo(State, JobId);
INSERT dbo.QueueDemo(State, Payload) VALUES ('R', N'first'), ('R', N'second');

Ejecute la reserva bajo READ COMMITTED sin transacción envolvente. La comprobación explicita esa condición. UPDLOCK coordina competidores, READPAST permite saltar filas bloqueadas y READCOMMITTEDLOCK solicita semántica de bloqueos cuando la base utiliza versionado Read Committed. Esta combinación pertenece a ese contrato y no debe copiarse a cualquier transacción SNAPSHOT.

IF @@TRANCOUNT <> 0 OR (@@OPTIONS & 2) = 2
    THROW 50000, 'Use autocommit for this example.', 1;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @claimed table
(JobId bigint, ClaimToken uniqueidentifier, Payload nvarchar(100));
DECLARE @token uniqueidentifier = NEWID();
;WITH candidate AS
( SELECT TOP (1) * FROM dbo.QueueDemo
  WITH (UPDLOCK, READPAST, READCOMMITTEDLOCK)
  WHERE State = 'R' ORDER BY JobId )
UPDATE candidate
SET State = 'W', ClaimToken = @token,
    LeaseUntil = DATEADD(minute, 5, SYSUTCDATETIME())
OUTPUT inserted.JobId, inserted.ClaimToken, inserted.Payload
INTO @claimed;
SELECT * FROM @claimed;

La CTE ordena candidatos por JobId, pero no promete FIFO global. Una tarea anterior bloqueada puede saltarse y el orden de finalización depende de su duración. READPAST no evita todos los bloqueos de página o esquema. Un resultado vacío significa que este intento no reservó ninguna fila disponible, no que haya desaparecido todo el trabajo pendiente.

OUTPUT INTO captura el identificador y token de la misma modificación. El SELECT final se ejecuta después del UPDATE en autocommit. Aun así, cualquier error debe tratarse como fallo: no procese resultados observados parcialmente. Pruebe reservas simultáneas y registre cada JobId junto con su token.

Recuperar tareas abandonadas

La concesión temporal establece vencimiento, pero no detiene físicamente al trabajador. Un proceso suspendido puede reanudarse después. Cada cambio de propietario debe reemplazar ClaimToken. La finalización comprueba JobId y token actual para impedir que un trabajador antiguo complete una tentativa nueva.

-- Parameters supplied from the successful claim:
-- @JobId bigint, @ClaimToken uniqueidentifier
UPDATE dbo.QueueDemo
SET State = 'D', LeaseUntil = NULL
WHERE JobId = @JobId AND ClaimToken = @ClaimToken AND State = 'W';
SELECT @@ROWCOUNT AS CompletedRows;

Cero filas modificadas significa que cambiaron propiedad o estado. No lo convierta silenciosamente en éxito. Defina extensiones mediante heartbeat que compruebe token, duración máxima y recuperación de concesiones vencidas. El recuperador también debe actualizar condicionalmente e invalidar el token anterior. Una actualización ciega basada en un SELECT previo reproduce la carrera original.

Hacer seguros los reintentos externos

La concesión no garantiza una sola ejecución de correo, pago o llamada remota. Un trabajador puede realizar el efecto externo y fallar antes de registrar finalización. Después del vencimiento otro volverá a intentarlo. Utilice un identificador de operación estable con un destino idempotente, o un procedimiento persistente de conciliación cuando el destino no pueda deduplicar.

Conserve historial de intentos y una política de fallos limitada. Un contenido permanentemente inválido debe pasar a estado de error con diagnóstico útil. Monitorice antigüedad de la tarea lista más vieja, concesiones vencidas, fallos repetidos y tiempo de finalización. La profundidad por sí sola puede ocultar una tarea importante atascada.

Antes del despliegue, termine un trabajador después de reservar, retrase otro más allá del vencimiento y pierda una respuesta final deliberadamente. Compruebe recuperación, rechazo de tokens antiguos y ausencia de efectos duplicados. Verifique también que una reserva vacía provoque una pausa limitada, evitando un bucle de consultas sin descanso. Estas pruebas describen la fiabilidad mejor que una prueba de rendimiento con trabajadores que nunca fallan.

Referencias técnicas: Microsoft Learn: Table hints · Microsoft Learn: OUTPUT.

Pregunta sobre este artículo

¿Tiene alguna pregunta sobre este tema?

Cuéntenos qué está evaluando o dónde tiene dificultades. Le responderemos con una recomendación práctica.

Inquiries are not enabled in this preview.

Hacer una pregunta sobre este artículo