SALVAR TRANSAÇÃO vs BEGIN TRANSACTION (SQL Server) como ninho de transações bem
-
14-12-2019 - |
Pergunta
Eu tenho um procedimento armazenado que necessita de definir um ponto de salvamento, de modo que ele pode, em determinadas circunstâncias, desfazer tudo que fez e retornar um código de erro para o chamador, ou aceitar/confirmar e retornar sucesso para o chamador.Mas eu preciso dele para trabalhar se o chamador já começou uma transação ou não.O doc é extremamente confuso sobre este assunto.Aqui está o que eu acho que vai funcionar, mas eu não estou certo de todas as ramificações.
A coisa é - este Stored Procedure (SP)
é chamado por outros.Então, eu não sei se eles já iniciado uma transação ou não...Mesmo se eu exigir que os usuários iniciar uma transação de usar o meu de SP, eu ainda tiver dúvidas sobre o uso adequado de Save Points
...
Meus SP vai testar se uma transação está em andamento, e se não, comece com um BEGIN TRANSACTION
.Se uma transação já está em progresso, em vez disso, criará um ponto de save com SAVE TRANSACTION MySavePointName
, e salve o fato foi isso que eu fiz.
Então, se eu tenho que reverter as minhas alterações, se eu fiz uma BEGIN TRANSACTION
mais cedo, então eu vou ROLLBACK TRANSACTION
.Se eu fiz o save point, então eu vou ROLLBACK TRANSACTION MySavePointName
.Este cenário parece um grande trabalho.
Aqui é onde eu fico um pouco confuso - se eu quiser manter o trabalho que eu fiz, se eu começar uma transação executarei COMMIT TRANSACTION
.Mas se eu criei um save point?Eu tentei COMMIT TRANSACTION MySavePointName
, mas, em seguida, o interlocutor tenta consolidar sua transação e recebe uma mensagem de erro:
A TRANSAÇÃO de confirmação de pedido tem não correspondente BEGIN TRANSACTION.
Então, eu estou querendo saber então um save point pode ser revertida (que funciona: ROLLBACK TRANSACTION MySavePointName
NÃO vai reverter a chamada transação).Mas talvez nunca precisa "commit" ele?Ele simplesmente fica lá, no caso de você precisar reverter para ele, mas vai embora depois que o original transação é confirmada (ou revertida)?
Se não há um "melhor" caminho para o "ninho" de uma transação, por favor, lançar um pouco de luz também.Eu ainda não descobri como ninho com BEGIN TRANSACTION
mas só de reversão ou de comprometer a minha transação interna.Parece ROLLBACK
sempre vai voltar para o topo de transação, enquanto COMMIT
simplesmente diminui @@trancount
.
Solução
Eu acredito que eu percebi tudo isso fora agora, então eu vou responder a minha própria pergunta...
Eu mesmo escrevi minhas conclusões se você quiser mais detalhes http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx
Então, meus SP começa com algo como isso, para iniciar uma nova transação se não houver nenhum, mas usar um Save Point, se um já está em andamento:
DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT
IF @startingTranCount > 0
SAVE TRANSACTION mySavePointName
ELSE
BEGIN TRANSACTION
-- …
Então, quando estiver pronto para aplicar as alterações, você só precisa comprometer-se começamos a transação nós mesmos:
IF @startingTranCount = 0
COMMIT TRANSACTION
E, finalmente, para reverter apenas as alterações até agora:
-- Roll back changes...
IF @startingTranCount > 0
ROLLBACK TRANSACTION MySavePointName
ELSE
ROLLBACK TRANSACTION
Outras dicas
Estendendo-se Brian B resposta.
Isso garante que o ponto de salvar o nome é único e usa o novo TRY/CATCH/JOGAR funcionalidades do SQL Server 2012.
DECLARE @mark CHAR(32) = replace(newid(), '-', '');
DECLARE @trans INT = @@TRANCOUNT;
IF @trans = 0
BEGIN TRANSACTION @mark;
ELSE
SAVE TRANSACTION @mark;
BEGIN TRY
-- do work here
IF @trans = 0
COMMIT TRANSACTION @mark;
END TRY
BEGIN CATCH
IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark;
THROW;
END CATCH
Eu tenho usado este tipo de gerenciador de transações no meu Procedimentos Armazenados :
CREATE PROCEDURE Ardi_Sample_Test
@InputCandidateID INT
AS
DECLARE @TranCounter INT;
SET @TranCounter = @@TRANCOUNT;
IF @TranCounter > 0
SAVE TRANSACTION ProcedureSave;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
/*
<Your Code>
*/
IF @TranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @TranCounter = 0
ROLLBACK TRANSACTION;
ELSE
IF XACT_STATE() <> -1
ROLLBACK TRANSACTION ProcedureSave;
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT @ErrorMessage = ERROR_MESSAGE();
SELECT @ErrorSeverity = ERROR_SEVERITY();
SELECT @ErrorState = ERROR_STATE();
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
GO