Eu tenho um serviço da Web que pode disparar uma cadeia de eventos para um pedido. Ele faz isso quando recebe uma mensagem informando (máximo de cerca de 2 por segundo).
O problema que tenho é que tenho mais de uma instância do Web Service e, às vezes, diferentes partes do pedido podem atingir cada instância exatamente ao mesmo tempo. Isso faz com que a cadeia de eventos seja disparada mais de uma vez para a ordem (ruim).
Então, eu estava pensando, eu poderia fazer uma tabela que é apenas o ID do pedido e um sinalizador dizendo se a "cadeia de eventos" já foi disparada.
Eu poderia então iniciar uma transação no meu código. Algo assim:
mySqlConnection.BeginTransaction(); // C# Code
Eu poderia selecionar a linha para o pedido em questão assim:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT OrderId, IsChainFired
FROM OrderedChainFlag WITH ( ROWLOCK, XLOCK, HOLDLOCK )
WHERE OrderId = 123456
Então, se IsChainFired for falso, dispara a "Cadeia de Eventos". Eu então executaria:
UPDATE OrderedChainFlag
SET IsChainFired = 1
WHERE OrderId = 123456
e execute (mesmo que IsChainFired seja verdadeiro):
myConnection.CommitTransaction(); // C# Code
Isso impediria que mais de uma instância do serviço lesse que a cadeia está livre para ser executada ao mesmo tempo.
Minha preocupação (e minha pergunta) é escalonamento de bloqueio . Se isso permanecer em bloqueios de linha, será uma solução fantástica para o meu problema. Mas se os bloqueios aumentarem para página ou tabela, terei acabado de criar um gargalo em meu sistema.
Então, há algo que eu possa fazer para garantir que isso permaneça no nível de bloqueio de linha? (ou é mesmo uma boa ideia usar o sistema de bloqueio de banco de dados como um semáforo como este?)
NOTA: OrderId seria a chave primária, o índice clusterizado e o índice de particionamento da tabela.
Você pode usar a
OUTPUT
cláusula para ver se a cadeia de eventos deve ser "disparada":Minha cama de teste:
Execute isto para ver a saída:
Use a
DataReader
para ler os resultados daUPDATE
instrução; se uma linha for retornada por.Read()
você sabe que a tabela foi atualizada e, efetivamente, você pode executar a cadeia de eventos.A execução da
UPDATE
instrução acima só fornecerá saída se ela realmente atualizar aIsChainFired
coluna para1
. Você pode provar isso executando a instrução de atualização duas vezes. O primeiro mostra a saída, o segundo não.Como essa instrução é atômica, uma atualização única será bem-sucedida, sem afetar o escalonamento de bloqueio, supondo que você tenha um bom índice em
OrderId
eIsChainFired
, e esse índice esteja configuradoWITH (ALLOW_ROW_LOCKS = ON)
.A segunda opção, que é uma pequena variação da anterior, seria não especificar o próximo
OrderID
a ser processado; em vez disso, basta selecionar o próximo que temIsChainFired = 0
:A
UPDATE
instrução, neste caso, retornaOrderID
para uma única linha diretamente doIX_OrderedChainFlag_IsFired
índice.Eu inseri mais de 750.000 linhas na tabela OrderedChainFlag em meu ambiente de teste e executei a
UPDATE TOP(1)
instrução acima, com o índice que mencionei no lugar, e obtive o seguinte plano:Um bônus (discutível) aqui é que você poderia retornar mais do que
TOP(1)
linhas para processamento, talvez usando vários encadeamentos em seu serviço da Web.Você provavelmente também deve ler esta pergunta , juntamente com as excelentes respostas sobre simultaneidade.