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.