Warum wird SQL Server nach dem Auslösen weiterhin ausgeführt, wenn xact_abort aktiviert ist?

87

Ich wurde gerade von etwas in TSQL überrascht. Ich dachte, wenn xact_abort aktiviert ist, rufe ich so etwas an

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

würde die Ausführung der gespeicherten Prozedur (oder eines beliebigen Stapels) stoppen.

Aber meine ADO.NET-Fehlermeldung hat genau das Gegenteil bewiesen. Ich habe sowohl die Raiserror-Fehlermeldung in der Ausnahmemeldung als auch die nächste Meldung erhalten.

Dies ist meine Problemumgehung (was sowieso meine Gewohnheit ist), aber es scheint nicht notwendig zu sein:

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

Die Dokumente sagen dies:

Wenn SET XACT_ABORT aktiviert ist und eine Transact-SQL-Anweisung einen Laufzeitfehler auslöst, wird die gesamte Transaktion beendet und zurückgesetzt.

Bedeutet das, dass ich eine explizite Transaktion verwenden muss?

Eric Z Bart
quelle
Gerade getestet und RAISERRORwird tatsächlich die Ausführung beenden, wenn der Schweregrad auf 17 oder 18 anstatt auf 16 eingestellt ist.
Reformiert am
1
Gerade getestet SQL Server 2012 und RAISERRORwird in der Tat nicht die Ausführung beenden, wenn der Schweregrad auf 17 oder 18 statt 16 eingestellt ist.
Ian Boyd
Der Grund wird von Erland Sommarskog (SQL Server MVP seit 2001) in Teil 2 seiner hervorragenden Serie Fehler- und Transaktionsbehandlung in SQL Server klar erklärt : "Hin und wieder habe ich das Gefühl, dass SQL Server absichtlich so konzipiert ist Wenn sie eine neue Version planen, fragen sie sich gegenseitig, was wir diesmal tun können, um die Benutzer zu verwirren. Manchmal gehen ihnen die Ideen ein wenig aus, aber dann sagt jemand, wir sollten etwas mit der Fehlerbehandlung anfangen! "
Umgekehrter Ingenieur

Antworten:

48

Dies ist By Design TM , wie Sie in Connect anhand der Antwort des SQL Server-Teams auf eine ähnliche Frage sehen können:

Danke für deine Rückmeldung. Die Set-Option XACT_ABORT hat standardmäßig keinen Einfluss auf das Verhalten der RAISERROR-Anweisung. Wir werden Ihr Feedback berücksichtigen, um dieses Verhalten für eine zukünftige Version von SQL Server zu ändern.

Ja, dies ist ein Problem für einige, die gehofft haben, dass RAISERRORein hoher Schweregrad (wie 16) mit einem SQL-Ausführungsfehler identisch ist - dies ist jedoch nicht der Fall.

Ihre Problemumgehung ist genau das, was Sie tun müssen, und die Verwendung einer expliziten Transaktion hat keine Auswirkungen auf das Verhalten, das Sie ändern möchten.

Philip Rieck
quelle
1
Danke Philip. Der Link, auf den Sie verwiesen haben, scheint nicht verfügbar zu sein.
Eric Z Beard
2
Link funktioniert gut, wenn Sie jemals danach suchen müssen, Titel "Lassen Sie RAISERROR mit XACT_ABORT arbeiten", Autor "jorundur", ID: 275308
JohnC
Link ist tot, ohne zwischengespeicherte Kopie von archive.org. Es ist für immer im Sand der Zeit verloren gegangen.
Ian Boyd
Diese Antwort ist eine gute Sicherung - mit einem Link zu den Dokumenten, in denen dieses Verhalten deutlich gemacht wird.
PCDEV
24

Wenn Sie einen Try / Catch-Block verwenden, führt eine Raiserror-Fehlernummer mit dem Schweregrad 11-19 dazu, dass die Ausführung zum Catch-Block springt.

Jeder Schweregrad über 16 ist ein Systemfehler. Um den folgenden Code zu demonstrieren, wird ein Try / Catch-Block eingerichtet und eine gespeicherte Prozedur ausgeführt, von der wir annehmen, dass sie fehlschlägt:

Angenommen, wir haben eine Tabelle [dbo]. [Fehler], um Fehler zu speichern. Angenommen, wir haben eine gespeicherte Prozedur [dbo]. [AssumeThisFails], die fehlschlägt, wenn wir sie ausführen

-- 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
Ninegrid
quelle
22

Verwenden Sie RETURNsofort danach RAISERROR()und es wird die Prozedur nicht weiter ausführen.

piyush
quelle
8
Möglicherweise möchten Sie vor dem Anruf rollback transactionanrufen return.
Mike Christian
1
Wahrscheinlich müssen Sie etwas in Ihrem
Fangblock
14

Wie in den Dokumenten für angegeben SET XACT_ABORT, sollte die THROWAnweisung anstelle von verwendet werden RAISERROR.

Die beiden verhalten sich etwas unterschiedlich . Wenn XACT_ABORTjedoch ON eingestellt ist, sollten Sie immer den THROWBefehl verwenden.

Möoz
quelle
25
Wenn Sie nicht über 2k12 verfügen (oder höher, wenn es herauskommt), ist keine THROW-Anweisung zu haben.
Jeff Moden