Este artigo clássico sobre segurança de concorrência foi claramente projetado para realizar o upsert de apenas uma linha por vez. Na minha situação, tenho uma entrada com valor de tabela e quero realizar o upsert de cada linha de forma segura em termos de concorrência. Sei que isso nem sempre é possível, mas quero chegar o mais perto possível. MERGE
Parece uma solução natural, mas desconfio dela e, na verdade, estou em uma situação em que é propensa a bugs . As duas abordagens restantes no artigo de Michael J. Swart são:
- Dentro de uma transação com dicas de bloqueio (atualização mais comum)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS
SET XACT_ABORT ON;
BEGIN TRAN
UPDATE TOP (1) dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
SET Etc = @Etc
WHERE Email = @Email;
IF (@@ROWCOUNT = 0)
BEGIN
INSERT dbo.AccountDetails ( Email, Etc )
VALUES ( @Email, @Etc );
END
COMMIT
- Dentro de uma transação com dicas de bloqueio (inserir mais comum)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS
SET XACT_ABORT ON;
BEGIN TRAN
INSERT dbo.AccountDetails ( Email, Etc )
SELECT @Email, @Etc
WHERE NOT EXISTS (
SELECT *
FROM dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
WHERE Email = @Email
)
IF (@@ROWCOUNT = 0)
BEGIN
UPDATE TOP (1) dbo.AccountDetails
SET Etc = @Etc
WHERE Email = @Email;
END
COMMIT
Eu poderia adaptar qualquer uma delas para usar variáveis de tabela (por exemplo, suspeito que IF (@@ROWCOUNT = 0)
precise ser totalmente removida), mas o uso de uma entrada com valor de tabela torna óbvio que devemos preferir a primeira ou a segunda solução? Se não, com base em quê a decisão deve ser tomada?
Faça a atualização primeiro e depois faça uma inserção com uma
where not exists
cláusula. Não é possível testar@@rowcount
porque algumas linhas podem ter sido atualizadas.Se você fizer a inserção primeiro, você também atualizará as linhas que acabou de inserir.
Problemas
MERGE
geralmente se manifestam com combinações de recursos (especialmente os relativamente novos) que resultam em planos de alteração de dados altamente complexos. Isso é uma pena, poisMERGE
é, sem dúvida, uma maneira conveniente de expressar coisas como um "upsert".Solução alternativa
Você pode considerar o seguinte:
INSTEAD OF
gatilho(s) para visualizaçãoINSERT
eUPDATE
ações.MERGE
contraponto à visão.Vantagens:
MERGE
vê um alvo simples , então o plano de alteração de dados não é complexo.Desvantagens:
OUTPUT
cláusula não funcionará para referências inseridas .Esta solução alternativa permite que você escreva um
MERGE
, mas tenha as ações resultantes executadas como inserções e atualizações simples separadas.As precauções usuais para simultaneidade
MERGE
ainda são necessárias.Usei uma visualização assumindo que você prefere não ter
INSTEAD OF
gatilhos na tabela base.*
INSTEAD OF
gatilhos não usam controle de versão de linha. Veja meu artigo, Coisas interessantes sobre gatilhos INSTEAD OF .Demonstração
Há melhorias a serem feitas (como a possibilidade de combinar os gatilhos). O código a seguir prioriza a clareza:
db<>violino
Linhas de tabela e amostra
Visualizar
INSTEAD OF INSERT
gatilho de visualizaçãoINSTEAD OF UPDATE
gatilho de visualizaçãoValor de tabela
MERGE
Resultados
MERGE
planoArrumar