it-swarm-eu.dev

Warum wird Sql Server nach einem Raiser weiter ausgeführt, wenn xact_abort aktiviert ist?

Ich wurde gerade von etwas in TSQL überrascht. Ich dachte, wenn xact_abort eingeschaltet wäre, würde ich so etwas aufrufen

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

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

Aber meine ADO.NET-Fehlermeldung hat genau das Gegenteil bewiesen. In der Ausnahmemeldung wurde sowohl die Fehlermeldung "RaiseRor" angezeigt als auch das nächste Problem, das danach auftrat.

Dies ist meine Problemumgehung (was sowieso meine Gewohnheit ist), aber es scheint nicht so, als ob es notwendig sein sollte:

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

Die Dokumente sagen Folgendes:

Wenn SET XACT_ABORT auf ON gesetzt ist und eine Transact-SQL-Anweisung einen Laufzeitfehler auslöst, wird die gesamte Transaktion beendet und ein Rollback ausgeführt.

Bedeutet das, dass ich eine explizite Transaktion verwenden muss?

84
Eric Z Beard

Dies ist beabsichtigtTMWie Sie auf Connect sehen können, beantwortet das SQL Server-Team eine ähnliche Frage:

Vielen Dank für dein Feedback. Die festgelegte XACT_ABORT-Option wirkt sich standardmäßig nicht auf das Verhalten der RAISERROR-Anweisung aus. 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, RAISERROR mit einem hohen Schweregrad (wie 16) wäre dasselbe wie ein SQL-Ausführungsfehler - ist es nicht.

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.

45
Philip Rieck

Wenn Sie einen try/catch-Block verwenden, bewirkt eine Fehlererhöhungsnummer mit dem Schweregrad 11-19, dass die Ausführung zum catch-Block springt.

Jeder Schweregrad über 16 ist ein Systemfehler. Um zu demonstrieren, richtet der folgende Code einen try/catch-Block ein und führt eine gespeicherte Prozedur aus, von der wir annehmen, dass sie fehlschlagen wird:

angenommen, wir haben eine Tabelle [dbo]. [Errors], 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
23
ninegrid

Verwenden Sie RETURN unmittelbar nach RAISERROR(), um die Prozedur nicht weiter auszuführen.

22
piyush

Wie in MSDN angegeben, sollte die THROW -Anweisung anstelle von RAISERROR verwendet werden.

Die beiden verhalten sich etwas anders . Wenn jedoch XACT_ABORT Auf ON gesetzt ist, sollten Sie immer den Befehl THROW verwenden.

13
Möoz