Pergunta

Eu tenho um aplicativo que usa números de incidentes (entre outros tipos de números). Esses números são armazenados em uma tabela chamada "número_setup", que contém o valor atual do contador.

Quando o aplicativo gera um novo incidente, ele tabela number_setup e obtém a linha do contador de números necessários (os contadores podem ser redefinidos diariamente, semanalmente etc. e são armazenados como INTs). Em seguida, aumenta o contador e atualiza a linha com o novo valor.

O aplicativo é multiuser (aproximadamente 100 usuários a qualquer momento, bem como trabalhos SQL que são executados e pegam 100 registros de incidentes e solicitam números de incidentes para cada um). A tabela de incidentes possui alguns números de incidentes duplicados, onde eles não devem ser duplicados.

Um PROC armazenado é usado para recuperar o próximo contador.

SELECT @Counter = counter, @ShareId=share_id, @Id=id
FROM Number_Setup
WHERE LinkTo_ID=@LinkToId
AND Counter_Type='I'

IF isnull(@ShareId,0) > 0
BEGIN 
    -- use parent counter
    SELECT @Counter = counter, @ID=id
    FROM Number_Setup
    WHERE Id=@ShareID
END

SELECT @NewCounter = @Counter + 1

UPDATE Number_Setup SET Counter = @NewCounter
WHERE id=@Id

Agora eu cercava esse bloco com uma transação, mas não tenho certeza de que 'ele resolverá 100% o problema, pois acho que ainda há bloqueios compartilhados, para que o balcão possa ser lido de qualquer maneira.

Talvez eu possa verificar se o contador não foi atualizado, na declaração de atualização

UPDATE Number_Setup SET Counter = @NewCounter
WHERE Counter = @Counter
IF @@ERROR = 0 AND @@ROWCOUNT > 0 
    COMMIT TRANSACTION
ELSE
    ROLLBACK TRANSACTION

Tenho certeza de que este é um problema comum com números de fatura em aplicativos financeiros etc.
Não posso colocar a lógica no código e usar o bloqueio nesse nível. Eu também trancei no Holdlock, mas não tenho certeza de sua aplicação. Deve ser colocado nas duas instruções selecionadas?

Como posso garantir que nenhuma duplicata seja criada?

Foi útil?

Solução

O truque é fazer a atualização do contador e ler em uma única operação atômica:

UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter 
WHERE id=@Id;

Porém, isso não atribui o novo contador a @NewCounter, mas o retorna como resultado para o cliente. Se você precisar atribuí -lo, use uma variável de tabela intermediária para gerar o novo contador:

declare @NewCounter int;
declare @tabCounter table (NewCounter int);
UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter)
WHERE id=@Id
SELECT @NewCounter = NewCounter FROM @tabCounter;

Isso resolve o problema de tornar o contador incremento atômico. Você ainda tem outras condições de corrida em seu procedimento, porque o linkto_id e o share_id ainda podem ser atualizados após a primeira seleção para que você possa incrementar o contador do item de link para errado, mas isso não pode ser resolvido apenas a partir deste exemplo de código, pois depende também no código que atualiza o Shared_id e/ou linkto_id.

BTW, você deve entrar no Habbit de nomear seus campos com um caso consistente. Se eles são nomeado consistentemente então você devo Use o caso de correspondência exato no código T-SQL. Seus scripts funcionam bem agora apenas porque você tem um servidor de agrupamento insensível de caso, se você implantar em um servidor de agrupamento sensível ao caso e seus scripts não correspondem ao caso exato dos erros de nomes de campo/tabelas seguirão em geral.

Outras dicas

Você já tentou usar GUIDs em vez de autoinncrements como seu identificador exclusivo?

Se você tiver a ablidade de modificar seu trabalho que obtém registros de mutiplos, eu mudaria o pensamento para que seu contador seja uma coluna de identidade. Então, quando você obtém o próximo registro, você pode simplesmente fazer uma inserção e obter a identidade da tabela @@. Isso garantiria que você obtenha o maior número. Você também teria que fazer uma dbccreed para redefinir o contador, em vez de apenas atualizar a tabela quando deseja redefinir a identidade. A única questão é que você teria que fazer 100 ou mais inserções como parte do seu trabalho SQL para obter um grupo de identidades. Isso pode ser uma sobrecarga demais, mas o uso de uma coluna de identidade é uma maneira de GuarenteDeed de obter números únicos.

Posso estar perdendo alguma coisa, mas parece que você está tentando reinventar a tecnologia que já foi resolvida pela maioria dos bancos de dados.

Em vez de ler e atualizar da coluna 'contador' na tabela Number_setup, por que você não usa apenas uma chave primária de incrementação automática para o seu contador? Você nunca terá um valor duplicado para uma chave primária.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top