O padrão é (sem tratamento de erros):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
WHERE ProductID = @ProductID;
IF @@ROWCOUNT = 0
BEGIN
INSERT #TProductSales(ProductID, StockQTY, ETA1)
VALUES(@ProductID, @StockQTY, @ETA1);
END
COMMIT TRANSACTION;
Você não precisa realizar uma leitura adicional da tabela #temp aqui. Você já está fazendo isso tentando a atualização. Para proteger de condições de corrida, você faz o mesmo que protegeria qualquer bloco de duas ou mais instruções que deseja isolar:você o envolveria em uma transação com um nível de isolamento apropriado (provavelmente serializável aqui, embora tudo apenas faz sentido quando não estamos falando de uma tabela #temp, já que por definição é serializada).
Você não está mais adiantado adicionando um
IF EXISTS
check (e você precisaria adicionar dicas de bloqueio para torná-lo seguro/serializável de qualquer maneira), mas você pode estar mais atrasado, dependendo de quantas vezes você atualizar as linhas existentes versus inserir novas. Isso poderia adicionar um monte de E/S extra. As pessoas provavelmente dirão para você usar
MERGE
(que na verdade é várias operações nos bastidores e também precisa ser protegida com serializável), peço que você não o faça. Eu coloco o porquê aqui:- Tenha cuidado com a instrução MERGE do SQL Server
Para um padrão de várias linhas (como um TVP), eu lidaria com isso da mesma maneira, mas não há uma maneira prática de evitar a segunda leitura como você pode com o caso de uma única linha. E não,
MERGE
também não evita. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE t SET t.col = tvp.col
FROM dbo.TargetTable AS t
INNER JOIN @TVP AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @TVP AS tvp
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TargetTable
WHERE ProductID = tvp.ProductID
);
COMMIT TRANSACTION;
Bem, acho que há uma maneira de fazer isso, mas não testei isso completamente:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
DECLARE @exist TABLE(ProductID int PRIMARY KEY);
UPDATE t SET t.col = tvp.col
OUTPUT deleted.ProductID INTO @exist
FROM dbo.TargetTable AS t
INNER JOIN @tvp AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @tvp AS t
WHERE NOT EXISTS
(
SELECT 1 FROM @exist
WHERE ProductID = t.ProductID
);
COMMIT TRANSACTION;
Em ambos os casos, você executa a atualização primeiro, caso contrário, atualizará todas as linhas que acabou de inserir, o que seria um desperdício.