【问题标题】:Stored Procedure Error Handling - Clean up but return original error存储过程错误处理 - 清理但返回原始错误
【发布时间】:2013-01-07 20:08:43
【问题描述】:

我正在编写一个存储过程,如果insert 失败,它需要清理一些数据。我希望它执行清理,但如果此插入失败,则返回原始错误(主要用于记录,因为我想确切地了解 insert 失败的原因)。基本上就像C# 中的throw;。有没有一种简单的方法可以做到这一点?

BEGIN TRY
    Insert into table (col1) values ('1")
END TRY
BEGIN CATCH
    --do clean up here
    --then throw original error
END TRY

这是可行/好的做法吗?在调用 proc 的应用程序代码中,我从应用程序的角度处理错误,但清理语句似乎更适合 proc 内部。

【问题讨论】:

  • 通常你会在 catch 块中回滚和清理。我个人认为这是存储过程中 catch 块的最佳用途之一。你只需要确保你的任何回滚/清理都不会产生更多错误,并且无论你试图清理什么,在你的错误之后都是可延展的。
  • 简短的回答是使用RAISERROR,因为在SQL Server 2012 之前没有THROW。这篇文章给出了长答案:simple-talk.com/sql/database-administration/…
  • 在 SQL Server 2012 中,您可以使用 THROW()。在 SQL Server 2008 中,您不能抛出/重新引发。
  • 您能解释一下所选答案实际上是如何解决这个问题的吗?您发现什么错误可以使用RAISERROR(不是RAISEERROR)成功重新引发?

标签: sql-server-2008 stored-procedures error-handling


【解决方案1】:

我通常会这样做:

IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[procedure_name]') AND ObjectProperty(id, N'IsProcedure') = 1)
    DROP PROCEDURE [dbo].[procedure_name]
GO

CREATE PROCEDURE [dbo].[procedure_name]
(
    @param1 VARCHAR(100)
   ,@param2 INT
)

AS

/*
*******************************************************************************
<Name>
[procedure_name]
</Name>

<Purpose>
[Purpose]
</Purpose>

<Notes>
</Notes>

<OutsideRef>
Called From: [Called From]
</OutsideRef>

<ChangeLog>
Change No:   Date:          Author:       Description:                          
_________    ___________    __________    _____________________________________
   001       [DATE]         [YOU]         Created.                           
</ChangeLog>
*******************************************************************************
*/
BEGIN
    SET NOCOUNT ON
    SET XACT_ABORT OFF -- Allow procedure to continue after error

    -- *****************************************
    -- Parameter string. Used for error handling
    -- *****************************************
    DECLARE  @ErrorNumber       INT 
            ,@ErrorMessage      VARCHAR(400)
            ,@ErrorSeverity     INT
            ,@ErrorState        INT
            ,@ErrorLine         INT
            ,@ErrorProcedure    VARCHAR(128)
            ,@ErrorMsg          VARCHAR(2000)
            ,@NestedProc        BIT = 1
            ,@Params            VARCHAR(255);    -- String representing parameters, make it an appropriate size given your parameters.

    --Be Careful of the CONVERT here, GUIDs (if you use them) need 36 characters, ints need 10, etc.        
    SET @Params = ''
                + CHAR(13) + '@param1 = ' + COALESCE(CONVERT(VARCHAR(100), @param1), 'NULL') 
                + CHAR(13) + '@param2 = ' + COALESCE(CONVERT(VARCHAR(10), @param2), 'NULL')

    BEGIN TRY
        --If you're using transactions, and want an 'all or nothing' approach, use this so that
        --you only start a single transaction in the outermost calling procedure (or handle 
        --that through your application layer)
        IF @@TRANCOUNT = 0
        BEGIN
            SET @NestedProc = 0
            BEGIN TRANSACTION
        END

        INSERT INTO [TABLE]
            (
             COL1
            ,COL2
            )
        VALUES
            (
             @param1
            ,@param2
            );

        --Commit the transaction if this is the outtermost procedure and if there is a transaction to rollback.
        IF @@TRANCOUNT > 0 AND @NestedProc = 0
        BEGIN
            COMMIT TRANSACTION
        END
    END TRY

    BEGIN CATCH
        --Roll back the transaction if this is the outtermost procedure and if there is a transaction to rollback.
        IF @@TRANCOUNT > 0 AND @NestedProc = 0
        BEGIN
            ROLLBACK TRANSACTION
        END

        -- Execute the error retrieval routine.
        SELECT 
            @ErrorNumber = ERROR_NUMBER(),
            @ErrorSeverity = ERROR_SEVERITY(),
            @ErrorProcedure = ERROR_PROCEDURE(),
            @ErrorState = ERROR_STATE(),
            @ErrorLine = ERROR_LINE(),
            @ErrorMessage = ERROR_MESSAGE();

        SET @ErrorMsg = 'Error Number   : ' + CAST(@ErrorNumber AS VARCHAR(5)) + CHAR(13)
                      + 'Procedure Name : ' + @ErrorProcedure + CHAR(13)
                      + 'Procedure Line : ' + CAST(@ErrorLine AS VARCHAR(5)) + CHAR(13)
                      + 'Error Message  : ' + @ErrorMessage + CHAR(13)
                      + 'Parameters     : ' + CHAR(13) + @Params + CHAR(13);

        --Raise the exception.
        RAISERROR (@ErrorMsg, @ErrorSeverity, @ErrorState);
    END CATCH
END
GO

这种类型的过程允许您使用事务嵌套过程(只要期望的效果是,如果在任何地方抛出错误,您最终会返回到外部过程然后回滚)。我认为这个模板无法处理的一个非常重要的场景是抛出一个严重到足以完全终止该过程的错误的情况。也许其他人可以在这方面加入。

【讨论】:

    【解决方案2】:

    假设我们使用的表 MyTable 定义为

    CREATE TABLE [dbo].[MyTable](
        [Col1] [int] NOT NULL
    ) ON [PRIMARY]
    

    我将使用类似于以下的过程。

    在插入失败的情况下,代码将进入 Catch 块,在那里可以执行和分配错误号/消息的检查。

    一旦分配了事务,就可以回滚并返回错误号/消息。

    您可能需要更改 RAISERROR 错误行中的 SQL Server 错误编号,具体取决于您正在执行的操作。

    CREATE PROCEDURE [dbo].[zTestProc]
    
    AS
    BEGIN
    
        SET NOCOUNT ON;
        DECLARE 
                @LocalError     INT,
                @ErrorMessage   VARCHAR(4000)
    
        BEGIN TRY
            BEGIN TRANSACTION TestTransaction
    
            Insert into MyTable(col1) values ('01/01/2002')
    
    
            COMMIT TRANSACTION TestTransaction
    
        END TRY
        BEGIN CATCH
    
            SELECT  @LocalError     =   ERROR_NUMBER(),
                    @ErrorMessage   =   ERROR_MESSAGE()
    
            IF( XACT_STATE()) <>0
            BEGIN
                ROLLBACK TRANSACTION TestTransaction
            END
    
            RAISERROR ('TestSP: %d: %s', 16, 1, @LocalError, @ErrorMessage) ;
            RETURN(0)
    
        END CATCH
    
    
    END
    

    【讨论】:

      【解决方案3】:

      试试下面的 sn-p。

      DECLARE @errNum int
      DECLARE @rowCount int
      
      BEGIN TRY
          INSERT INTO [TABLE] (COL1) VALUES ('1")
      END TRY
      BEGIN CATCH
          SET @errNum = @@ERROR
          SET @rowCount = @@ROWCOUNT
          RAISEERROR(@errNum)
      END CATCH
      

      【讨论】:

      • 我想你的意思是RAISERROR,而不是RAISE
      • (1) nvarchar 没有长度? (2) 什么是RAISEERROR(额外的E),为什么语法缺少严重性/状态? (3) 你试过吗?我得到,例如,Msg 2732, Level 16, State 1, Line 9 Error number 8xxx is invalid. The number must be from 13000 through 2147483647 and it cannot be 50000.
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-07
      • 2013-05-17
      • 2010-12-03
      • 2015-01-02
      • 1970-01-01
      • 1970-01-01
      • 2011-07-05
      相关资源
      最近更新 更多