为什么当xact_abort打开时,Sql Server在raiserror之后仍继续执行?


87

我只是对TSQL中的某些东西感到惊讶。我以为如果打开xact_abort,调用类似

raiserror('Something bad happened', 16, 1);

将停止执行存储过程(或任何批处理)。

但是我的ADO.NET错误消息恰恰相反。在异常消息中我同时收到了raiserror错误消息,以及在此之后发生的下一件事。

这是我的解决方法(无论如何,这是我的习惯),但似乎没有必要:

if @somethingBadHappened
    begin;
        raiserror('Something bad happened', 16, 1);
        return;
    end;

文档说:

当SET XACT_ABORT为ON时,如果Transact-SQL语句引发运行时错误,则整个事务将终止并回滚。

这是否意味着我必须使用显式事务?


刚刚经过测试,并且RAISERROR如果严重性设置为17或18而不是16,则实际上将终止执行
进行了改革

2
刚刚测试SQL Server 2012中,并RAISERROR会在事实上没有,如果严重性设置为17或18终止执行,而不是16
伊恩·博伊德

1
Erland Sommarskog(自2001年以来一直是SQL Server MVP)在其出色的系列《 SQL Server中的错误和事务处理》的第2部分中清楚地解释了原因:“每隔一段时间,我都会感到有意将SQL Server设计为当他们计划要发布一个新版本时,他们会互相问我们这次可以使用户感到困惑吗?有时他们会有些想法,但是有人说让我们做一些错误处理!”
逆向工程师

实际上,即使将严重性设置为17或18或19,@ IanBoyd也不会停止执行。更有趣的是,如果您在Messages选项卡中查看,您将看不到(X rows affected)PRINT消息,我想这是一个完整的谎言
百利厄斯

Answers:


48

这是By Design TM,您可以在Connect上看到SQL Server团队对类似问题的回答:

感谢您的反馈意见。根据设计,XACT_ABORT设置选项不会影响RAISERROR语句的行为。我们将考虑您的反馈意见,以在将来的SQL Server版本中修改此行为。

是的,对于那些希望RAISERROR严重程度很高(例如16)与SQL执行错误相同的人来说,这有点问题-并非如此。

解决方法只是您需要做的事情,使用显式事务对您要更改的行为没有任何影响。


1
谢谢菲利普。您引用的链接似乎不可用。
Eric Z Beard

2
链接工作正常,如果您需要搜索,标题为“让RAISERROR与XACT_ABORT一起使用”,作者为“ jorundur”,ID:275308
JohnC

链接无效没有archive.org缓存的副本。它已经永远迷失在时间的沙子里。
伊恩·博伊德

这个答案是一个很好的备份-带有指向该行为的文档链接
pcdev

25

如果使用try / catch块,严重性为11-19的raiserror错误号将导致执行跳转到catch块。

任何高于16的严重性都是系统错误。为了演示以下代码,我们设置了一个try / catch块并执行了一个我们认为会失败的存储过程:

假设我们有一个表[dbo]。[错误]用来保存错误假设我们有一个存储过程[dbo]。[AssumeThisFails],当我们执行它时它将失败

-- first lets build a temporary table to hold errors
if (object_id('tempdb..#RAISERRORS') is null)
 create table #RAISERRORS (ErrorNumber int, ErrorMessage varchar(400), ErrorSeverity int, ErrorState int, ErrorLine int, ErrorProcedure varchar(128));

-- this will determine if the transaction level of the query to programatically determine if we need to begin a new transaction or create a save point to rollback to
declare @tc as int;
set @tc = @@trancount;
if (@tc = 0)
 begin transaction;
else
 save transaction myTransaction;

-- the code in the try block will be executed
begin try
 declare @return_value = '0';
 set @return_value = '0';
 declare
  @ErrorNumber as int,
  @ErrorMessage as varchar(400),
  @ErrorSeverity as int,
  @ErrorState as int,
  @ErrorLine as int,
  @ErrorProcedure as varchar(128);


 -- assume that this procedure fails...
 exec @return_value = [dbo].[AssumeThisFails]
 if (@return_value <> 0)
  raiserror('This is my error message', 17, 1);

 -- the error severity of 17 will be considered a system error execution of this query will skip the following statements and resume at the begin catch block
 if (@tc = 0)
  commit transaction;
 return(0);
end try


-- the code in the catch block will be executed on raiserror("message", 17, 1)
begin catch
  select
   @ErrorNumber = ERROR_NUMBER(),
   @ErrorMessage = ERROR_MESSAGE(),
   @ErrorSeverity = ERROR_SEVERITY(),
   @ErrorState = ERROR_STATE(),
   @ErrorLine = ERROR_LINE(),
   @ErrorProcedure = ERROR_PROCEDURE();

  insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
   values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);

  -- if i started the transaction
  if (@tc = 0)
  begin
   if (XACT_STATE() <> 0)
   begin
     select * from #RAISERRORS;
    rollback transaction;
    insert into [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     select * from #RAISERRORS;
    insert [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
    return(1);
   end
  end
  -- if i didn't start the transaction
  if (XACT_STATE() = 1)
  begin
   rollback transaction myTransaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(2); 
  end
  else if (XACT_STATE() = -1)
  begin
   rollback transaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(3);
  end
 end catch
end

22

RETURN之后立即使用RAISERROR(),它将不再执行该过程。


8
您可能需要在致电rollback transaction之前致电return
Mike Christian

1
可能您可能需要在catch块中做些什么
sqluser15年

14

正如在docs上指出的那样SET XACT_ABORTTHROW应使用语句代替RAISERROR

两者的行为略有不同。但是当XACT_ABORT设置为ON时,则应始终使用该THROW命令。


25
如果您没有2k12(或更高的2k12),则没有THROW语句。
Jeff Moden
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.