SAVE TRANSACTION 与 BEGIN TRANSACTION (SQL Server) 如何很好地嵌套事务
-
14-12-2019 - |
题
我有一个存储过程,需要设置一个保存点,以便在某些情况下它可以撤消它所做的一切并向调用者返回错误代码,或者接受/提交它并向调用者返回成功。但无论调用者是否已经开始交易,我都需要它工作。该文档在这个主题上非常令人困惑。这是我认为可行的方法,但我不确定所有的后果。
事情是——这个 Stored Procedure (SP)
被别人称为。所以我不知道他们是否已经开始交易......即使我要求用户启动交易才能使用我的 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
其他提示
延伸 布莱恩·B 的回答.
这可确保保存点名称是唯一的,并使用 SQL Server 2012 的新 TRY/CATCH/THROW 功能。
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