【问题标题】:Exit and rollback everything in script on error出错时退出并回滚脚本中的所有内容
【发布时间】:2011-02-24 01:30:33
【问题描述】:

我有一个 TSQL 脚本,它做了很多数据库结构调整,但是当出现故障时让它通过并不安全。

说清楚:

  • 使用 MS SQL 2005
  • 它不是存储过程,只是一个脚本文件 (.sql)

我拥有的是以下顺序的东西

BEGIN TRANSACTION
    ALTER Stuff
    GO

    CREATE New Stuff
    GO

    DROP Old Stuff
    GO
IF @@ERROR != 0
    BEGIN
  PRINT 'Errors Found ... Rolling back'
  ROLLBACK TRANSACTION
  RETURN
    END
ELSE
     PRINT 'No Errors ... Committing changes'
     COMMIT TRANSACTION

只是为了说明我正在处理的内容...无法详细说明 现在,问题...

当我引入错误(以测试事情是否回滚)时,我得到一条语句,即 ROLLBACK TRANSACTION 找不到相应的 BEGIN TRANSACTION。 这让我相信,当真的出错并且交易已经被杀死时,有些事情发生了。 我还注意到脚本没有完全退出错误,因此 DID 尝试在错误发生后执行每条语句。 (当我没想到新表出现时,我注意到了这一点,因为它应该已经回滚)

【问题讨论】:

    标签: sql-server sql-server-2005 tsql transactions


    【解决方案1】:

    当错误发生时,事务自动回滚,当前批次中止。

    但是,执行将继续到下一批。因此,执行错误后批次中的所有内容。然后当您稍后检查错误时,您会尝试回滚已经回滚的事务。

    此外,要停止整个脚本,而不仅仅是当前批次,您应该使用:

    raiserror('Error description here', 20, -1) with log
    

    详情请参阅my answer here

    所以你需要在每批之后检查@error,我认为这样的事情应该可以工作:

    BEGIN TRANSACTION
    GO
    
    ALTER Stuff
    GO
    
    if @@error != 0 raiserror('Script failed', 20, -1) with log
    GO
    
    CREATE New Stuff
    GO
    
    if @@error != 0 raiserror('Script failed', 20, -1) with log
    GO
    
    DROP Old Stuff
    GO
    
    if @@error != 0 raiserror('Script failed', 20, -1) with log
    GO
    
    PRINT 'No Errors ... Committing changes'
    COMMIT TRANSACTION
    

    【讨论】:

    • 这基本上是我使用的,但形式如下: if @@error 0 or @@trancount = 0 begin if @@trancount > 0 rollback transaction set noexec on end
    • 这是否适用于服务器级事务,例如DROP LOGIN?我想更改登录所属的域,并将其从所有数据库中删除,但需要重新创建,同时确保如果不是每个事务都成功提交,则不会删除任何内容。这将导致权限映射错误等,并且将是一场灾难。
    • 它似乎工作:我刚刚运行了一个脚本begin transaction; drop login [pc\user]; raiserror('Script failed', 20, -1) with log - 登录没有被删除(确保你在这样做之前以sa 登录,否则 raiserror 将失败并且脚本将继续!)
    • 只有当 stuff 是单个语句时才有效。 @@ERROR 只返回执行的最后一条语句的结果,所以如果 stuff 由多条语句组成,只有最后一条语句的错误才会导致执行这种错误处理。 TRY...CATCH 更好。
    • 好点,不应该是每批一次,而是每条语句一次。是的,try/catch 更好,但请注意问题标有sql-server-2005 - try/catch 不是一个选项。
    【解决方案2】:

    尝试使用 RETURN。这将立即退出脚本或过程,并且不会执行以下任何语句。您可以将它与 BEGIN、ROLLBACK 和 COMMIT TRANSACTION 语句结合使用来撤消任何数据损坏:

        BEGIN
        BEGIN TRANSACTION
    
        <first batch>
        IF @@error <> 0
            begin
            RAISERROR ('first batch failed',16,-1)
            ROLLBACK TRANSACTION
            RETURN
            end
    
        <second batch>
        IF @@error <> 0
            begin
            RAISERROR ('second batch failed',16,-1)
            ROLLBACK TRANSACTION
            RETURN
            end
    
        PRINT 'WIN!'
        COMMIT TRANSACTION
        END
    

    【讨论】:

      【解决方案3】:

      我没有使用 raiseerror 解决方案,因为我没有管理员权限,所以它失败了。我使用事务处理扩展了 noexec 开/关解决方案,如下所示:

      set noexec off
      
      begin transaction
      go
      
      <First batch, do something here>
      go
      if @@error != 0 set noexec on;
      
      <Second batch, do something here>
      go
      if @@error != 0 set noexec on;
      
      <... etc>
      
      declare @finished bit;
      set @finished = 1;
      
      SET noexec off;
      
      IF @finished = 1
      BEGIN
          PRINT 'Committing changes'
          COMMIT TRANSACTION
      END
      ELSE
      BEGIN
          PRINT 'Errors occured. Rolling back changes'
          ROLLBACK TRANSACTION
      END
      

      显然编译器“理解”IF 中的@finished 变量,即使出现错误并且执行被禁用。但是,仅当未禁用执行时,该值才设置为 1。因此,我可以很好地提交或回滚相应的事务。

      【讨论】:

        【解决方案4】:

        您可以尝试这样的事情...如果您正在使用 Try 块...错误级别 16(或大多数应用程序错误)会立即将控制权转移到 CATCH 块,而无需在 try 中执行任何进一步的语句阻止...

            Begin Transaction
        
        Begin Try
        
                            --  Do your Stuff
        
                If (@@RowCount <> 1) -- Error condition
                Begin
                    Raiserror('Error Message',16,1)
                End
        
        
            Commit
        End Try
        Begin Catch
            IF @@Trancount > 0
            begin
                Rollback Transaction
            End
        
            Declare @ErrMsg varchar(4000), @Errseverity int
        
            SELECT @ErrMsg = ERROR_MESSAGE(),
                  @ErrSeverity = ERROR_SEVERITY()
        
            RAISERROR(@ErrMsg, @ErrSeverity, 1)     
        End Catch
        

        希望这会有所帮助...

        【讨论】:

        【解决方案5】:
        SET XACT_ABORT ON
        BEGIN TRAN
        
        -- Batch 1
        
        GO
        
        if @@TRANCOUNT = 0 
        SET NOEXEC ON;
        GO
        
        -- Batch 2
        
        GO
        
        if @@TRANCOUNT = 0 
        SET NOEXEC ON;
        GO
        
        -- Batch 3
        
        GO
        
        if @@TRANCOUNT > 0 
        COMMIT
        GO
        

        【讨论】:

          猜你喜欢
          • 2011-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多