Evitar alterações perdidas com rowversion no SQL Server
Proteja formulários com rowversion, detecte atualizações concorrentes e trate conflitos sem sobrescrever silenciosamente o trabalho de outra pessoa.
Duas pessoas abrem a mesma descrição de produto. A primeira corrige uma indicação de preço e salva. A segunda altera a pontuação de uma cópia antiga e envia o formulário inteiro. As duas requisições funcionam, mas a segunda elimina a primeira correção. Uma transação envolvendo cada UPDATE não impede essa perda: a leitura desatualizada aconteceu antes.
Incluir a versão esperada na gravação
A requisição precisa informar qual versão o usuário leu. Uma coluna rowversion fornece um token binário de oito bytes gerado pelo banco. Retorne esse token junto dos campos editáveis e exija seu envio ao salvar. A chave primária e o token esperado devem fazer parte da condição do mesmo UPDATE.
Consultar o token, compará-lo na aplicação e depois executar uma atualização incondicional deixa uma janela de concorrência. Outra sessão pode gravar entre essas etapas. O exemplo simula dois editores em uma conexão: ambos capturam a versão inicial, mas o primeiro salvamento impede que a condição do segundo encontre a linha.
CREATE TABLE #Draft
(
DraftId int NOT NULL PRIMARY KEY,
Title nvarchar(100) NOT NULL,
Revision rowversion NOT NULL
);
INSERT #Draft (DraftId, Title) VALUES (1, N'Initial title');
DECLARE @SeenByA binary(8), @SeenByB binary(8);
SELECT @SeenByA = Revision, @SeenByB = Revision
FROM #Draft WHERE DraftId = 1;
UPDATE #Draft SET Title = N'Editor A'
OUTPUT inserted.DraftId, inserted.Title, inserted.Revision
WHERE DraftId = 1 AND Revision = @SeenByA;
UPDATE #Draft SET Title = N'Editor B'
WHERE DraftId = 1 AND Revision = @SeenByB;
DECLARE @Changed int = @@ROWCOUNT;
SELECT @Changed AS RowsChanged;
SELECT DraftId, Title, Revision FROM #Draft;
DROP TABLE #Draft;
O segundo UPDATE afeta zero linhas, e o título continua Editor A. O primeiro OUTPUT retorna o token novo, que pode acompanhar a resposta de sucesso. Se usar @@ROWCOUNT, capture o valor imediatamente, pois instruções posteriores podem substituí-lo. A chave primária garante que a requisição altere no máximo uma linha.
Trate o token como um valor binário opaco. Uma API JSON pode escolher Base64 ou hexadecimal de tamanho fixo. Decodifique exatamente oito bytes e utilize um parâmetro binário. Não transforme o token em um número JavaScript. Ele não representa uma data e o cliente não deve calcular seu próximo valor. Mantenha uma coluna datetime2 separada para apresentar um horário legível de alteração.
Preservar o trabalho no conflito
Zero linhas alteradas significa que a combinação de chave e versão não estava disponível. A causa pode ser outra edição, uma exclusão ou uma restrição de autorização. O resultado sozinho não identifica qual situação ocorreu. Inclua a autorização na condição de escrita ou em uma verificação transacional igualmente confiável, sem expor registros restritos por uma consulta de diagnóstico.
Para uma pessoa autorizada, preserve os valores enviados antes de buscar a versão atual. Uma tela de resolução pode mostrar os valores originais, a proposta do usuário e o estado vigente. Simplesmente recarregar a página e descartar o texto ainda não salvo resolve a integridade do banco, mas cria outro problema para quem estava trabalhando.
Não repita automaticamente um formulário desatualizado com o token mais recente. Isso transforma a proteção em sobrescrita silenciosa. Certas operações permitem um contrato mais específico: incrementar um contador atomicamente evita substituir um total lido anteriormente. Expresse a intenção do comando em vez de representar qualquer mudança como salvamento completo de documento.
Cobrir a unidade de negócio inteira
Um token protege uma linha. Uma tela que altera o cabeçalho de um pedido e seus itens não detecta uma mudança independente de item verificando apenas o cabeçalho. Cada alteração relevante precisa avançar uma versão compartilhada, ou os tokens dos itens alterados precisam ser comparados na mesma transação. Se a operação exige sucesso integral, um único conflito deve desfazer todas as mudanças relacionadas.
Triggers merecem um teste de integração. OUTPUT apresenta os valores anteriores à execução das triggers AFTER. Uma trigger que atualiza novamente a mesma linha pode avançar o token outra vez. Nesse caso, leia o token final dentro da transação depois da trigger. Só confirme o sucesso após o commit; receber uma linha de OUTPUT não comprova que a gravação foi confirmada.
Teste salvamentos simultâneos, exclusão seguida de edição e nova tentativa depois de perder a resposta da rede. Registre conflitos separadamente de falhas do servidor. Uma frequência elevada pode revelar formulários que substituem campos demais ou tarefas de fundo que atualizam linhas sem necessidade. Essa observação ajuda a melhorar o desenho das operações e oferece uma forma objetiva de acompanhar a experiência de edição.
Referências técnicas: Microsoft Learn: rowversion · Microsoft Learn: OUTPUT clause.