SAVE TRANSACTION vs BEGIN TRANSACTION (SQL Server), как правильно вкладывать транзакции
-
14-12-2019 - |
Вопрос
У меня есть хранимая процедура, которой необходимо установить точку сохранения, чтобы она могла при определенных обстоятельствах отменить все, что она сделала, и вернуть код ошибки вызывающей стороне или принять/зафиксировать ее и вернуть успех вызывающей стороне.Но мне нужно, чтобы он работал независимо от того, начал ли уже вызывающий абонент транзакцию или нет.Документ чрезвычайно запутан по этому вопросу.Вот что, я думаю, сработает, но я не уверен во всех последствиях.
Дело в том - это Stored Procedure (SP)
зовут другие.Так что я не знаю, начали они транзакцию или нет...Даже если я требую, чтобы пользователи начали транзакцию для использования моего SP, у меня все равно остаются вопросы о правильном использовании Save Points
...
Мой SP проверит, выполняется ли транзакция, а если нет, запустит ее с помощью BEGIN TRANSACTION
.Если транзакция уже выполняется, вместо этого будет создана точка сохранения с SAVE TRANSACTION MySavePointName
, и за исключением того факта, что это то, что я сделал.
Тогда, если мне придется откатить мои изменения, если я сделал BEGIN TRANSACTION
раньше, тогда я ROLLBACK TRANSACTION
.Если я сделал точку сохранения, то я ROLLBACK TRANSACTION MySavePointName
.Кажется, этот сценарий отлично работает.
Вот здесь я немного запутался - если я хочу сохранить проделанную работу, если я начал транзакцию, я ее выполню. COMMIT TRANSACTION
.А если бы я создал точку сохранения?Я пытался COMMIT TRANSACTION MySavePointName
, но затем вызывающая сторона пытается зафиксировать транзакцию и получает ошибку:
Запрос COMMIT TRANSACTION не имеет соответствующей BEGIN TRANSACTION.
Вот мне и интересно - точку сохранения можно откатить (это работает: ROLLBACK TRANSACTION MySavePointName
НЕ откатывает транзакцию вызывающего абонента).Но, возможно, никогда не нужно «совершать» это?Он просто остается там на случай, если вам понадобится вернуться к нему, но исчезает после фиксации (или отката) исходной транзакции?
Если есть «лучший» способ «вложить» транзакцию, пожалуйста, пролейте немного света.Я не придумал, как совместить BEGIN TRANSACTION
но только откатить или зафиксировать мою внутреннюю транзакцию.Кажется ROLLBACK
всегда будет откатываться к верхней транзакции, в то время как COMMIT
просто уменьшает @@trancount
.
Решение
Думаю, теперь я все это понял, поэтому отвечу на свой вопрос...
Я даже опубликовал свои выводы в блоге, если вам нужна более подробная информация: http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx
Итак, мой SP начинается примерно с этого: начать новую транзакцию, если ее нет, но использовать точку сохранения, если она уже выполняется:
DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT
IF @startingTranCount > 0
SAVE TRANSACTION mySavePointName
ELSE
BEGIN TRANSACTION
-- …
Затем, когда вы будете готовы зафиксировать изменения, вам нужно будет зафиксировать их только в том случае, если мы сами начали транзакцию:
IF @startingTranCount = 0
COMMIT TRANSACTION
И, наконец, чтобы откатить только ваши изменения:
-- Roll back changes...
IF @startingTranCount > 0
ROLLBACK TRANSACTION MySavePointName
ELSE
ROLLBACK TRANSACTION
Другие советы
Расширение Ответ Брайана Б..
Это гарантирует уникальность имени точки сохранения и использование новых функций TRY/CATCH/THROW 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
Я использовал этот тип менеджера транзакций в своих хранимых процедурах:
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