ERROR_PROCEDURE() 为 SP_EXECUTESQL 执行的代码返回 NULL
ERROR_PROCEDURE() returning NULL for code executed by SP_EXECUTESQL
我有一个带有 TRY CATCH
语句的存储过程,在该 TRY CATCH
中我正在调用另一个抛出错误的存储过程。抛出并捕获异常,但是如果错误发生在被调用的存储过程中,则它不会显示在 ERROR_PROCEDURE()
中,它被设置为 NULL。似乎是因为在调用的存储过程中执行了 Dynamic 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
我曾尝试将存储过程嵌套在它自己的 TRY CATCH
中,但这会导致 TRANSACTION ROLLBACK
问题。
ERROR_PROCEDURE()
是 NULL 是因为它超出了范围吗?有没有办法设置它?
似乎是因为在调用的存储过程中执行了动态 SQL。有办法处理吗?
根据更新的问题和评论进行编辑
ERROR_PROCEDURE() will not return a procedure name for SQL executed via SP_EXECUTESQL. Logically, if it did, it would return 'SP_EXECUTESQL' :). See this Connect entry "TRY/CATCH: ERROR_PROCEDURE() does not report name of procedure if error occured in dynamic SQL”,特别是微软回复中的这句话;
Since there is no name associated with the ad hoc SQL, ERROR_PROCEDURE
will return NULL for errors raised from the execution level of the ad
hoc 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.
问题是对 MyInnerError
的子进程调用没有失败;这是 MyInnerError
内的调用失败,但 MyInnerError
完全成功。
MyInnerError
成功完成,因为您没有像在外部过程中那样通过 TRY
/ CATCH
结构捕获错误并报告失败。
那个,来自 Dynamic SQL 的错误自然不会设置 ERROR_PROCEDURE()
。
你所有的 procs 都应该有 TRY
/ CATCH
结构并且 CATCH
块应该使用 RAISERROR 或 THROW(取决于 SQL 服务器的版本你正在使用)这样你就可以将错误冒泡到调用范围。
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;
IF (ERROR_PROCEDURE() IS NULL)
块用于捕获没有 proc 生成错误的情况。单独调用 ;THROW;
会弹出当前错误信息,在本例中是 NULL
for ERROR_PROCEDURE()
.
如果您想自己测试一下,只需 运行 下面的 SQL 它创建了 3 个存储过程,所有这些都使用上面显示的结构。最内层的过程 (ErrorTest1) 使用 SELECT 1/0;
作为查询调用 sp_executesql
。 "divide by zero" 错误被 TRY / CATCH 捕获。 ERROR_PROCEDURE()
函数 returns NULL
因为它是 Dynamic SQL 产生了错误。因此,调用 RAISERROR
(技术上调用 ;THROW 50505, @ErrMessage, @ErrState;
也可以)以向调用进程指示当前 proc 生成了错误。
测试设置:
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
运行 测试:
EXEC dbo.ErrorTest3;
Returns:
5个结果集:
3a
2a
1a
<empty>
ErrorProcedure ErrorState ErrorSeverity
ErrorTest1 1 16
并且在 "Messages" 选项卡中:
Msg 50000, Level 16, State 1, Procedure ErrorTest1, Line 36
Divide by zero error encountered.
我有一个带有 TRY CATCH
语句的存储过程,在该 TRY CATCH
中我正在调用另一个抛出错误的存储过程。抛出并捕获异常,但是如果错误发生在被调用的存储过程中,则它不会显示在 ERROR_PROCEDURE()
中,它被设置为 NULL。似乎是因为在调用的存储过程中执行了 Dynamic 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
我曾尝试将存储过程嵌套在它自己的 TRY CATCH
中,但这会导致 TRANSACTION ROLLBACK
问题。
ERROR_PROCEDURE()
是 NULL 是因为它超出了范围吗?有没有办法设置它?
似乎是因为在调用的存储过程中执行了动态 SQL。有办法处理吗?
根据更新的问题和评论进行编辑
ERROR_PROCEDURE() will not return a procedure name for SQL executed via SP_EXECUTESQL. Logically, if it did, it would return 'SP_EXECUTESQL' :). See this Connect entry "TRY/CATCH: ERROR_PROCEDURE() does not report name of procedure if error occured in dynamic SQL”,特别是微软回复中的这句话;
Since there is no name associated with the ad hoc SQL, ERROR_PROCEDURE will return NULL for errors raised from the execution level of the ad hoc 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.
问题是对 MyInnerError
的子进程调用没有失败;这是 MyInnerError
内的调用失败,但 MyInnerError
完全成功。
MyInnerError
成功完成,因为您没有像在外部过程中那样通过 TRY
/ CATCH
结构捕获错误并报告失败。
那个,来自 Dynamic SQL 的错误自然不会设置 ERROR_PROCEDURE()
。
你所有的 procs 都应该有 TRY
/ CATCH
结构并且 CATCH
块应该使用 RAISERROR 或 THROW(取决于 SQL 服务器的版本你正在使用)这样你就可以将错误冒泡到调用范围。
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;
IF (ERROR_PROCEDURE() IS NULL)
块用于捕获没有 proc 生成错误的情况。单独调用 ;THROW;
会弹出当前错误信息,在本例中是 NULL
for ERROR_PROCEDURE()
.
如果您想自己测试一下,只需 运行 下面的 SQL 它创建了 3 个存储过程,所有这些都使用上面显示的结构。最内层的过程 (ErrorTest1) 使用 SELECT 1/0;
作为查询调用 sp_executesql
。 "divide by zero" 错误被 TRY / CATCH 捕获。 ERROR_PROCEDURE()
函数 returns NULL
因为它是 Dynamic SQL 产生了错误。因此,调用 RAISERROR
(技术上调用 ;THROW 50505, @ErrMessage, @ErrState;
也可以)以向调用进程指示当前 proc 生成了错误。
测试设置:
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
运行 测试:
EXEC dbo.ErrorTest3;
Returns:
5个结果集:
3a
2a
1a
<empty>
ErrorProcedure ErrorState ErrorSeverity
ErrorTest1 1 16
并且在 "Messages" 选项卡中:
Msg 50000, Level 16, State 1, Procedure ErrorTest1, Line 36
Divide by zero error encountered.