Digamos que você tenha o seguinte código (ignore que é horrível):
BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else
A meu ver, isso NÃO está gerenciando a concorrência corretamente. Só porque você tem uma transação, não significa que alguém não lerá o mesmo valor que você fez antes de chegar à sua declaração de atualização.
Agora, deixando o código como está (percebo que isso é melhor tratado como uma única instrução ou ainda melhor usando uma coluna de incremento automático / identidade) - quais são as maneiras certas de fazê-lo lidar com a concorrência corretamente e evitar condições de corrida que permitem que dois clientes obtenham o mesmo valor de identificação?
Tenho certeza de que adicionar um WITH (UPDLOCK, HOLDLOCK)
ao SELECT fará o truque. O nível de isolamento de transação SERIALIZABLE parece funcionar bem, pois nega que mais alguém leia o que você fez até o tran terminar ( UPDATE : isso é falso. Veja a resposta de Martin). Isso é verdade? Ambos irão funcionar igualmente bem? Um prefere o outro?
Imagine fazer algo mais legítimo do que uma atualização de ID - algum cálculo com base em uma leitura que você precisa atualizar. Pode haver muitas tabelas envolvidas, algumas das quais você escreverá e outras que não. Qual é a melhor prática aqui?
Depois de escrever essa pergunta, acho que as dicas de bloqueio são melhores porque você está bloqueando apenas as tabelas necessárias, mas eu apreciaria a opinião de qualquer pessoa.
PS E não, eu não sei a melhor resposta e realmente quero entender melhor! :)
update
que possam ser baseados em dados obsoletos? Neste último caso, você pode usar arowversion
coluna para verificar se a linha a ser atualizada não foi alterada desde que foi lida.Respostas:
Apenas abordando o
SERIALIZABLE
aspecto do nível de isolamento. Sim, isso funcionará, mas com risco de conflito.Duas transações poderão ler a linha simultaneamente. Eles não se bloquearão, pois terão um
S
bloqueio de objeto ou um bloqueio de índiceRangeS-S
dependente da estrutura da tabela e esses bloqueios são compatíveis . Mas eles se bloquearão ao tentar adquirir os bloqueios necessários para a atualização (IX
bloqueio ou índice de objeto,RangeS-U
respectivamente), o que levará a um conflito.O uso de uma
UPDLOCK
dica explícita serializará as leituras, evitando assim o risco de conflito.fonte
IX
paraX
o próprio heap. Curiosamente, nenhuma linha é qualificada, portanto, nenhum bloqueio de linha é removido. Não sei por que é necessário oX
bloqueio.Eu acho que a melhor abordagem para você seria realmente expor seu módulo a alta concorrência e ver por si mesmo. Às vezes, o UPDLOCK sozinho é suficiente e não há necessidade do HOLDLOCK. Às vezes, o sp_getapplock funciona muito bem. Eu não faria nenhuma declaração geral aqui - às vezes, adicionar mais um índice, gatilho ou exibição indexada altera o resultado. Precisamos enfatizar o código do teste e ver por nós mesmos caso a caso.
Eu escrevi vários exemplos de testes de estresse aqui
Edit: para melhor conhecimento dos internos, você pode ler os livros de Kalen Delaney. No entanto, os livros podem ficar fora de sincronia, como em qualquer outra documentação. Além disso, há muitas combinações a serem consideradas: seis níveis de isolamento, muitos tipos de bloqueios, índices clusterizados / não clusterizados e quem sabe o que mais. Isso é muitas combinações. Além disso, o SQL Server é de código fechado, portanto, não podemos baixar o código-fonte, depurá-lo e assim por diante - isso seria a melhor fonte de conhecimento. Qualquer outra coisa pode estar incompleta ou desatualizada após o próximo lançamento ou service pack.
Portanto, você não deve decidir o que funciona para o seu sistema sem o seu próprio teste de estresse. Tudo o que você leu, pode ajudá-lo a entender o que está acontecendo, mas você deve provar que os conselhos que leu funcionam para você. Eu não acho que alguém possa fazer isso por você.
fonte
Nesse caso em particular, a adição de um
UPDLOCK
bloqueio ao sistemaSELECT
impediria de fato anomalias. A adição deHOLDLOCK
não é necessária, pois um bloqueio de atualização é mantido durante a transação, mas confesso que o incluí como um hábito (possivelmente ruim) no passado.Não há boas práticas. Sua escolha de controle de concorrência deve se basear nos requisitos do aplicativo. Alguns aplicativos / transações precisam ser executados como se tivessem propriedade exclusiva do banco de dados, evitando anomalias e imprecisões a todo custo. Outros aplicativos / transações podem tolerar algum grau de interferência um do outro.
Edit: @ O comentário de AlexKuznetsov me levou a reler a pergunta e remover o erro muito óbvio em minha resposta. Nota para si mesmo em postagens noturnas.
fonte