SQL Server na prática

Filas SQL Server: reservar, confirmar e recuperar

Reserve trabalho atomicamente, entenda READPAST e impeça que um worker antigo sobrescreva o estado de um novo proprietário.

Um SELECT seguido de um UPDATE separado não reserva trabalho com segurança. Dois workers podem escolher a mesma linha antes de qualquer um alterar o estado. A fila precisa de uma transição atômica e de um commit curto. O trabalho de negócio potencialmente demorado deve acontecer depois.

Reservar em uma instrução

Crie a tabela uma vez em um banco descartável. O índice ajuda a encontrar tarefas prontas sem percorrer continuamente todo o histórico concluído. O conteúdo é pequeno de propósito; uma fila real pode guardar referências para documentos maiores.

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');

Execute a reserva sob READ COMMITTED sem transação externa. A verificação explicita essa condição. UPDLOCK coordena concorrentes, READPAST permite pular linhas bloqueadas e READCOMMITTEDLOCK solicita semântica de bloqueios quando o banco usa versionamento Read Committed. Essa combinação pertence a esse contrato e não deve ser transportada para qualquer transação 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;

A CTE ordena candidatos por JobId, mas não promete FIFO global. Uma tarefa anterior bloqueada pode ser pulada e a ordem de conclusão depende da duração do trabalho. READPAST não contorna todos os bloqueios de página ou esquema. Uma saída vazia significa apenas que essa tentativa não reservou uma linha disponível.

OUTPUT INTO captura identificador e token alterados pela própria instrução. O SELECT final ocorre depois do UPDATE em autocommit. Mesmo assim, qualquer erro deve ser considerado falha; não execute trabalho com base em saída parcialmente observada. Teste reservas simultâneas e registre cada JobId acompanhado do token.

Recuperar trabalho abandonado

A concessão define vencimento, mas não interrompe fisicamente o worker. Um processo suspenso pode voltar depois do prazo. Cada troca de proprietário precisa substituir ClaimToken. A conclusão deve conferir JobId e token atual para impedir que um worker antigo finalize uma tentativa nova.

-- 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;

Zero linhas alteradas indica mudança de propriedade ou estado. Não transforme esse resultado silenciosamente em sucesso. Defina extensões por heartbeat com verificação do token, duração máxima e recuperação de concessões vencidas. O recuperador também precisa atualizar condicionalmente e invalidar o token antigo. Uma atualização cega baseada em SELECT anterior recria a corrida original.

Tornar efeitos externos repetíveis com segurança

Uma concessão não garante execução única de e-mail, pagamento ou chamada remota. O worker pode concluir o efeito externo e falhar antes de registrar sucesso. Após o vencimento, outro tentará novamente. Use um identificador estável de operação com destino idempotente ou um procedimento durável de reconciliação quando o destino não deduplicar.

Mantenha histórico de tentativas e uma política limitada de falhas. Conteúdo definitivamente inválido deve alcançar um estado de erro com diagnóstico útil. Monitore idade da tarefa pronta mais antiga, concessões vencidas, falhas repetidas e tempo de conclusão. A profundidade sozinha não revela uma tarefa importante permanentemente travada.

Antes de publicar, encerre um worker após a reserva, atrase outro além do vencimento e perca uma resposta de conclusão de propósito. Verifique recuperação, rejeição de tokens antigos e ausência de efeitos duplicados. Confira também se uma reserva vazia provoca uma pausa limitada em vez de um loop sem descanso consultando o banco. Esses testes descrevem a confiabilidade melhor que um benchmark com workers que nunca falham.

Referências técnicas: Microsoft Learn: Table hints · Microsoft Learn: OUTPUT.

Pergunte sobre este artigo

Tem alguma dúvida sobre este tema?

Conte o que você está avaliando ou onde encontrou dificuldades. Responderemos com uma recomendação prática.

Inquiries are not enabled in this preview.

Fazer uma pergunta sobre este artigo