我正在尝试改进当前系统的错误处理,以生成更有意义的错误消息。我有一个“根”存储过程,它对其他嵌套存储过程进行多次调用。
在根sp中,XACT_ABORT
设置为ON
,但在嵌套过程中,XACT_ABORT
设置为OFF
。我想从较低级别的过程中捕获特定错误,而不是获取根过程的错误。
我经常看到错误uncommittable transaction is detected at the end of the batch, the transaction is being rolled back.
将这些“混合”环境与XACT_ABORTs
一起使用会有什么影响吗?
另外,如果您对高级错误处理有任何建议,那将非常感激。我想我想使用sp_executesql
所以我可以传递参数来获取错误输出,而不必修改所有存储过程并使用RAISERROR
来调用父过程的CATCH
块。 / p>
答案 0 :(得分:5)
SET XACT_ABORT的设置在执行或运行时设置,而不是在 解析时间
即。 XACT_ABORT
不会从创建会话“复制”到每个过程,因此任何未在内部明确设置此选项的PROC将在运行时从环境会话继承该设置,这可能是灾难性的。
FWIW,作为一般规则,我们始终确保全局XACT_ABORT
为ON并进行lint检查以确保我们的PROC都没有覆盖此设置。
请注意,XACT_ABORT
不是银弹,例如 - 已经raised by your PROC with RAISERROR的错误不会终止批处理。但是,THROW keyword in SQL 2012
正如您所建议的那样,根据Remus Rusanu's observation,结构化异常处理(TRY / CATCH)是一种更加干净和健壮的异常处理机制。
答案 1 :(得分:1)
一种保持XACT_ABORT并在任何情况下获取错误的方法或提交如果在调用可能调用其他SP的SP时一切正常:两个sp和三个测试作为示例
create PROCEDURE [dbo].[myTestProcCalled]
(
@testin int=0
)
as
begin
declare @InnerTrans int
set XACT_ABORT on;
set @InnerTrans = @@trancount;
PRINT '02_01_Trancount='+cast (@InnerTrans as varchar(2));
begin try
if (@InnerTrans = 0)
begin
PRINT '02_02_beginning trans';
begin transaction
end
declare @t2 int
set @t2=0
PRINT '02_03_doing division'
set @t2=10/@testin
PRINT '02_04_doing AfterStuff'
if (@InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '02_05_Committing'
commit transaction
end
PRINT '02_05B_selecting calledValue=' +cast(@t2 as varchar(20))
select @t2 as insidevalue
end try
begin catch
PRINT '02_06_Catching Errors from called'
declare @ErrorMessage nvarchar(4000);
declare @ErrorNumber int;
declare @ErrorSeverity int;
declare @ErrorState int;
select @ErrorMessage = error_message(), @ErrorNumber = error_number(), @ErrorSeverity = error_severity(), @ErrorState = error_state();
if (@InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '02_07_Rolbacking'
rollback transaction
end
PRINT '02_08_Rising Error'
raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
go
create PROCEDURE [dbo].[myTestPCalling]
(
@test int=0
,@testinside int=0
)
as
begin
declare @InnerTrans int
set XACT_ABORT on;
set @InnerTrans = @@trancount;
PRINT '01_01_Trancount='+cast (@InnerTrans as varchar(2));
begin try
if (@InnerTrans = 0)
begin
PRINT '01_02_beginning trans';
begin transaction
end
declare @t2 int
set @t2=0
PRINT '01_03_doing division'
set @t2=10/@test
PRINT '01_04_calling inside sp'
execute [dbo].[myTestProcCalled]
@testin = @testinside
--
PRINT '01_05_doing AfterStuff'
if (@InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '01_06_Committing'
commit transaction
PRINT '01_06B_selecting callerValue=' +cast(@t2 as varchar(20))
select @t2 as outsidevalue
end
end try
begin catch
PRINT '01_07_Catching Errors from Caller'
declare @ErrorMessage nvarchar(4000);
declare @ErrorNumber int;
declare @ErrorSeverity int;
declare @ErrorState int;
select @ErrorMessage = error_message(), @ErrorNumber = error_number(), @ErrorSeverity = error_severity(), @ErrorState = error_state();
if (@InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '01_08_Rolbacking'
rollback transaction
end
PRINT '01_09_Rising Error'
raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
----test 1 :result OK----
USE [PRO-CGWEB]
GO
DECLARE @return_value int
EXEC @return_value = [dbo].[myTestPCalling]
@test =2
,@testinside = 2
SELECT 'Return Value' = @return_value
GO
----test2 :error in caller ----
USE [PRO-CGWEB]
GO
DECLARE @return_value int
EXEC @return_value = [dbo].[myTestPCalling]
@test =0
,@testinside = 2
SELECT 'Return Value' = @return_value
GO
----test3 :error in calling ----
USE [PRO-CGWEB]
GO
DECLARE @return_value int
EXEC @return_value = [dbo].[myTestPCalling]
@test =2
,@testinside = 0
SELECT 'Return Value' = @return_value
GO