2012-03-15 100 views
12

我有一个存储过程,需要设置一个保存点,以便它可以,在某些情况下,撤消它所做的一切,并返回一个错误代码给调用者,或接受/提交并将成功返回给调用者。但是,无论呼叫者是否已经开始交易,我都需要它。该文件在这个问题上非常混乱。这是我认为会起作用的,但我不确定所有的后果。SAVE TRANSACTION vs BEGIN TRANSACTION(SQL Server)如何嵌套事务很好

东西是 - 这个Stored Procedure (SP)被别人调用。所以我不知道他们是否已经开始交易...即使我要求用户开始交易以使用我的SP,我仍然有关于正确使用Save Points的问题...

我的SP将测试交易是否正在进行,如果没有,请从BEGIN TRANSACTION开始。如果一个交易已经在进行中,它将会创建一个保存点,并且保存这个事实,这就是我所做的。

然后如果我必须回滚我的更改,如果我早些时候做了BEGIN TRANSACTION,那么我会ROLLBACK TRANSACTION。如果我做了保存点,那么我会ROLLBACK TRANSACTION MySavePointName。这种情况似乎很好。

这里我有点困惑 - 如果我想保持我所做的工作,如果我开始交易,我将执行COMMIT TRANSACTION。但是,如果我创建了一个保存点?我试过COMMIT TRANSACTION MySavePointName,但随后呼叫者尝试提交其交易,并得到一个错误:

The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

所以我不知道那么 - 保存点可以回滚(即作品:ROLLBACK TRANSACTION MySavePointName将不会回滚调用者的交易)。但也许人们从不需要“承诺”呢?它只停留在那里,以防需要回滚到原始事务,但在原始事务被提交(或回滚)后会消失?

如果有一种“更好”的方式来“嵌套”一个交易,请说明一下。我还没有想出如何嵌套BEGIN TRANSACTION,但只有回滚或提交我的内部交易。似乎ROLLBACK将始终回滚到最高交易,而COMMIT只是递减@@trancount

+0

你发现可能是值得张贴作为一个答案。 – 2012-03-16 08:49:22

+0

@Andriy好的,那就是我所做的 - 删除我的编辑并将其用作答案。谢谢。 – 2012-03-16 21:13:22

回答

19

我相信我现在已经想通了这一切了,所以我会回答我的问题...

我甚至在博客中我发现,如果你在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 
+0

有任何理由在'save transaction'后面没有启动一个新的嵌套事务? – sotn 2017-05-05 10:51:29

+0

下面是关于嵌套事务的文章的链接。如果提交嵌套事务,它会降低嵌套事务计数但不提交任何内容。因此,如果外部事务已提交,则所有内容如果回滚,则会回滚所有内容,包括您的“已提交”内容。如果嵌套事务执行回滚,它将自动回滚和外部事务。如果您只想回滚自己的交易而不影响您已经处于的任何交易,请使用此处描述的技术。 – 2017-05-05 16:12:08

+0

这是关于嵌套事务的文章:https://technet.microsoft.com/en-us/library/ms189336%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396 – 2017-05-05 16:12:50

10

扩展Brian B's answer

这可确保保存点名称是唯一的,并使用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 
+0

我还没回来在这篇文章中有一段时间,但喜欢你可以在下一个查询中重复使用的模板。不错的提升。 – 2015-10-06 02:03:21

+0

我在MSDN上看到了在try块内放置begin transaction语句的建议。这样做会导致一些问题或完全安全吗? – 2017-02-08 21:33:40

+0

我对catch块中的if语句感到困惑。为什么在xact state = -1时回滚事务?它不是<> - 1? – 2017-08-14 09:56:36

2

我已经使用这种类型的事务管理器在我的存储过程:

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 
相关问题