Pergunta

Este exigirá algumas explicações.O que fiz foi criar uma fila de mensagens personalizada específica no SQL Server 2005.Eu tenho uma tabela com mensagens que contêm carimbos de data e hora para confirmação e conclusão.O procedimento armazenado que os chamadores executam para obter a próxima mensagem na fila também reconhece a mensagem.Até agora tudo bem.Bem, se o sistema está enfrentando uma enorme quantidade de transações (milhares por minuto), não é possível que uma mensagem seja reconhecida por outra execução do procedimento armazenado enquanto outra está preparada para isso?Deixe-me ajudar mostrando meu código SQL no processo armazenado:

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

--Select the entire message
...
...

No código acima, outro procedimento armazenado em execução ao mesmo tempo não poderia obter o mesmo ID e tentar reconhecê-lo ao mesmo tempo?Eu poderia (ou deveria) implementar algum tipo de bloqueio para evitar que outro proc armazenado reconheça mensagens que outro proc armazenado está consultando?

Uau, alguma dessas coisas fazia sentido?É um pouco difícil colocar em palavras...

Foi útil?

Solução

Algo assim

--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId =   ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

-- some error checking
commit tran

--Select the entire message
...
...

Outras dicas

Este parece ser o tipo de situação em que OUTPUT pode ser útil:

-- Acknowledge and grab the next message
declare @message table (
    -- ...your `ActionMessages` columns here...
)
update ActionMessages
set    AcknowledgedTime = getdate()
output INSERTED.* into @message
where  ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
  and  AcknowledgedTime is null

-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...

Lá, atualizamos e capturamos a linha na mesma operação, o que informa ao otimizador de consulta exatamente o que estamos fazendo, permitindo que ele escolha o bloqueio mais granular possível e o mantenha pelo menor tempo possível.(Embora o prefixo da coluna seja INSERTED, OUTPUT são como gatilhos, expressos em termos de UPDATE sendo como excluir a linha e inserir a nova.)

Eu precisaria de mais informações sobre o seu ActionMessages e UnacknowledgedDemands tabelas (views/TVFs/qualquer coisa), sem contar um maior conhecimento do bloqueio automático do SQL Server, para dizer se isso and AcknowledgedTime is null cláusula é necessária.Ele existe para se defender contra uma condição de corrida entre a subseleção e a atualização.Tenho certeza de que não seria necessário se estivéssemos selecionando ActionMessages em si (por exemplo, where AcknowledgedTime is null com um top no update, em vez da subseleção em UnacknowledgedDemands).Espero que mesmo que seja desnecessário, seja inofensivo.

Observe que OUTPUT está no SQL Server 2005 e superior.Isso é o que você disse que estava usando, mas se fosse necessária compatibilidade com instalações geriátricas do SQL Server 2000, você gostaria de seguir outro caminho.

@Kilhoffer:

Todo o lote SQL é analisado antes da execução, para que o SQL saiba que você fará uma atualização na tabela e também fará uma seleção nela.

Editar:Além disso, o SQL não bloqueará necessariamente a tabela inteira - poderá bloquear apenas as linhas necessárias.Ver aqui para obter uma visão geral do bloqueio no SQL Server.

Em vez do bloqueio explícito, que muitas vezes é escalado pelo SQL Server para uma granularidade maior do que o desejado, por que não tentar esta abordagem:

declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands

update ActionMessages
  set AcknowledgedTime = getdate()
  where ActionMessageId = @MessageId and AcknowledgedTime is null

if @@rowcount > 0
  /* acknoweldge succeeded */
else
  /* concurrent query acknowledged message before us,
     go back and try another one */

Quanto menos você bloquear, maior será a simultaneidade.

Você realmente deveria processar as coisas uma por uma?Você não deveria apenas fazer com que o SQL Server reconhecesse todas as mensagens não confirmadas com a data de hoje e as retornasse?(tudo também em uma transação, é claro)

Leia mais sobre o bloqueio de seleção do SQL Server aqui e aqui.O SQL Server tem a capacidade de invocar um bloqueio de tabela em uma seleção.Nada acontecerá à mesa durante a transação.Quando a transação for concluída, quaisquer inserções ou atualizações serão resolvidas automaticamente.

Se você deseja agrupar seu código em uma transação, o SQL Server tratará do bloqueio das linhas ou tabelas apropriadas.

begin transaction

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

commit transaction

--Select the entire message
...
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top