ERROR_PROCEDURE()为SP_EXECUTESQL执行的代码返回NULL

时间:2015-02-10 12:43:53

标签: sql-server stored-procedures try-catch

我有一个带有TRY CATCH语句的存储过程,并且在该TRY CATCH中我正在调用另一个抛出错误的存储过程。抛出并捕获异常但是如果错误在被调用的存储过程中,则不会在ERROR_PROCEDURE()中显示,它将设置为NULL。似乎原因是动态SQL在被调用的存储过程中执行。

ALTER PROC dbo.MyError AS
BEGIN
    SET NOCOUNT,  XACT_ABORT ON;
    BEGIN TRY
        BEGIN TRAN
            --do stuff here
            --SQL CODE
            SELECT 'HELLO' AS hello

            --then call sproc
            EXEC dbo.MyInnerError
        COMMIT TRANSACTION 
    END TRY 
    BEGIN CATCH 
        IF (@@TRANCOUNT > 0)
           BEGIN
              ROLLBACK TRANSACTION 
           END 
        SELECT 
            ERROR_NUMBER() AS ErrorNumber,
            ERROR_SEVERITY() AS ErrorSeverity,
            ERROR_STATE() AS ErrorState,
            ERROR_PROCEDURE() AS ErrorProcedure,
            ERROR_LINE() AS ErrorLine,
            ERROR_MESSAGE() AS ErrorMessage; 
       END CATCH
END
GO

ALTER PROC dbo.MyInnerError AS
BEGIN
    DECLARE @SQl nvarchar(50) = 'SELECT 1/0 as DYNAMIC_FAIL';
    EXEC SP_EXECUTESQL @SQl;
END

EXEC dbo.MyError
GO

我已尝试将sproc嵌套在自己的TRY CATCH中,但这会导致TRANSACTION ROLLBACK问题。

ERROR_PROCEDURE是否为空,因为它超出了范围?有没有办法设置这个?

似乎原因是由于动态SQL在被调用的存储过程中执行。有办法解决这个问题吗?

2 个答案:

答案 0 :(得分:1)

根据更新的问题和评论进行修改

ERROR_PROCEDURE()不会返回通过SP_EXECUTESQL执行的SQL的过程名称。从逻辑上讲,如果确实如此,它将返回' SP_EXECUTESQL' :)。请参阅此Connect条目" TRY/CATCH: ERROR_PROCEDURE() does not report name of procedure if error occured in dynamic SQL",特别是Microsoft的回复中的这句话;

  

由于没有与ad hoc SQL关联的名称,ERROR_PROCEDURE   对于从广告执行级别引发的错误,将返回NULL   特别的SQL。


我敲了一个非常快速的测试,它适用于我(SQL Server 2012);

CREATE PROC dbo.MyError AS
BEGIN
    SET NOCOUNT,  XACT_ABORT ON;
    BEGIN TRY
        BEGIN TRAN
            --do stuff here
            --SQL CODE

            --then call sproc
            EXEC dbo.MyInnerError
        COMMIT TRANSACTION 
    END TRY 
    BEGIN CATCH 
        IF (@@TRANCOUNT > 0)
           BEGIN
              ROLLBACK TRANSACTION 
           END 
        SELECT 
            ERROR_NUMBER() AS ErrorNumber,
            ERROR_SEVERITY() AS ErrorSeverity,
            ERROR_STATE() AS ErrorState,
            ERROR_PROCEDURE() AS ErrorProcedure,
            ERROR_LINE() AS ErrorLine,
            ERROR_MESSAGE() AS ErrorMessage; 
       END CATCH
END
GO

CREATE PROC dbo.MyInnerError AS
BEGIN
    ;THROW 51000, 'This is my only error.', 1;
END
GO

EXEC dbo.MyError
GO

结果是;

ErrorNumber ErrorSeverity ErrorState  ErrorProcedure   ErrorLine   ErrorMessage            
----------- ------------- ----------- ---------------- ----------- ------------------------
51000       16            1           MyInnerError     4           This is my only error.

答案 1 :(得分:0)

问题是对MyInnerError的子进程调用没有失败; <{1}}内的来电失败,但MyInnerError完全成功。

MyInnerError已成功完成,因为您没有通过MyInnerError / TRY结构捕获错误并报告类似于外部过程中的失败。

那,来自动态SQL的错误自然不会设置CATCH

所有的proc都应该具有ERROR_PROCEDURE() / TRY结构,CATCH块应该使用RAISERROR或THROW(取决于您使用的SQL Server版本),以便你可以将错误冒充到调用范围。

CATCH

DECLARE @InNestedTransaction BIT = 0; BEGIN TRY IF (@@TRANCOUNT > 0) BEGIN SET @InNestedTransaction = 1; END; ELSE BEGIN BEGIN TRAN; END; ... one or more SQL statements ... COMMIT; END TRY BEGIN CATCH IF (@InNestedTransaction = 0) BEGIN ROLLBACK; END; IF (ERROR_PROCEDURE() IS NULL) BEGIN DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(), @ErrState TINYINT = ERROR_STATE(), @ErrSeverity TINYINT = ERROR_SEVERITY(); RAISERROR(@ErrMessage, @ErrSeverity, @ErrState); RETURN; END; ;THROW; -- introduced in SQL Server 2012 ---- If using SQL Server 2008, replace the above (from "IF" through "THROW") ---- with the following. -- DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(); -- RAISERROR(@ErrMessage, 16, 1); -- RETURN; END CATCH; 块用于捕获这样的情况,其中没有proc生成错误。单独调用IF (ERROR_PROCEDURE() IS NULL)会使当前错误信息冒泡,在这种情况下;THROW;NULL


如果您想自己测试,只需运行以下SQL,它创建3个存储过程,所有这些过程都使用上面显示的结构。最内层的过程(ErrorTest1)使用ERROR_PROCEDURE()作为查询来调用sp_executesql。 “除以零”错误被TRY / CATCH捕获。 SELECT 1/0;函数返回ERROR_PROCEDURE(),因为它是生成错误的动态SQL。因此,调用NULL(技术上调用RAISERROR也可以),以向调用进程指示当前proc生成错误。

测试设置:

;THROW 50505, @ErrMessage, @ErrState;

运行测试:

IF (OBJECT_ID(N'ErrorTest3') IS NOT NULL)
BEGIN
  DROP PROCEDURE ErrorTest3;
END;
IF (OBJECT_ID(N'ErrorTest2') IS NOT NULL)
BEGIN
  DROP PROCEDURE ErrorTest2;
END;
IF (OBJECT_ID(N'ErrorTest1') IS NOT NULL)
BEGIN
  DROP PROCEDURE ErrorTest1;
END;
GO

CREATE PROCEDURE dbo.ErrorTest1
AS
SET NOCOUNT ON;

DECLARE @InNestedTransaction BIT = 0;

BEGIN TRY
  IF (@@TRANCOUNT > 0)
  BEGIN
    SET @InNestedTransaction = 1;
  END;
  ELSE
  BEGIN
    BEGIN TRAN;
  END;

  SELECT '1a';
  EXEC sp_executesql N'SELECT 1/0 AS [ForceError];';
  SELECT '1b';

  COMMIT;

END TRY
BEGIN CATCH
  IF (@InNestedTransaction = 0)
  BEGIN
    ROLLBACK;
  END;

  IF (ERROR_PROCEDURE() IS NULL)
  BEGIN
    DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
            @ErrState TINYINT = ERROR_STATE(),
            @ErrSeverity TINYINT = ERROR_SEVERITY();
    RAISERROR(@ErrMessage, @ErrSeverity, @ErrState);
    RETURN;
  END;

  ;THROW; -- introduced in SQL Server 2012

END CATCH;
GO

CREATE PROCEDURE dbo.ErrorTest2
AS
SET NOCOUNT ON;

DECLARE @InNestedTransaction BIT = 0;

BEGIN TRY
  IF (@@TRANCOUNT > 0)
  BEGIN
    SET @InNestedTransaction = 1;
  END;
  ELSE
  BEGIN
    BEGIN TRAN;
  END;

  SELECT '2a';
  EXEC dbo.ErrorTest1;
  SELECT '2b';

  COMMIT;

END TRY
BEGIN CATCH
  IF (@InNestedTransaction = 0)
  BEGIN
    ROLLBACK;
  END;

  IF (ERROR_PROCEDURE() IS NULL)
  BEGIN
    DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
            @ErrState TINYINT = ERROR_STATE(),
            @ErrSeverity TINYINT = ERROR_SEVERITY();
    RAISERROR(@ErrMessage, @ErrSeverity, @ErrState);
    RETURN;
  END;

  ;THROW; -- introduced in SQL Server 2012

END CATCH;
GO

CREATE PROCEDURE dbo.ErrorTest3
AS
SET NOCOUNT ON;

DECLARE @InNestedTransaction BIT = 0;

BEGIN TRY
  IF (@@TRANCOUNT > 0)
  BEGIN
    SET @InNestedTransaction = 1;
  END;
  ELSE
  BEGIN
    BEGIN TRAN;
  END;

  SELECT '3a';
  EXEC dbo.ErrorTest2;
  SELECT '3b';

  COMMIT;

END TRY
BEGIN CATCH
  IF (@InNestedTransaction = 0)
  BEGIN
    ROLLBACK;
  END;

  SELECT ERROR_PROCEDURE() AS [ErrorProcedure],
         ERROR_STATE() AS [ErrorState],
         ERROR_SEVERITY() AS [ErrorSeverity];

  IF (ERROR_PROCEDURE() IS NULL)
  BEGIN
    DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
            @ErrState TINYINT = ERROR_STATE(),
            @ErrSeverity TINYINT = ERROR_SEVERITY();
    RAISERROR(@ErrMessage, @ErrSeverity, @ErrState);
    RETURN;
  END;

  ;THROW; -- introduced in SQL Server 2012

END CATCH;
GO

<强>返回:

5个结果集:

EXEC dbo.ErrorTest3;

在“消息”标签中:

  

Msg 50000,Level 16,State 1,Procedure ErrorTest1,Line 36
  除以遇到的零错误。