Perché Sql Server continua a essere eseguito dopo raiserror quando xact_abort è attivo?


87

Sono appena stato sorpreso da qualcosa in TSQL. Ho pensato che se xact_abort fosse attivo, chiamando qualcosa come

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

interromperà l'esecuzione della stored procedure (o di qualsiasi batch).

Ma il mio messaggio di errore ADO.NET ha appena dimostrato il contrario. Ho ricevuto sia il messaggio di errore di errore nel messaggio di eccezione, sia la cosa successiva che si è interrotta dopo.

Questa è la mia soluzione alternativa (che è comunque la mia abitudine), ma non sembra che dovrebbe essere necessario:

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

I documenti dicono questo:

Quando SET XACT_ABORT è ON, se un'istruzione Transact-SQL genera un errore di runtime, l'intera transazione viene terminata e annullata.

Significa che devo utilizzare una transazione esplicita?


Appena testato e RAISERRORdi fatto terminerà l'esecuzione se la gravità è impostata su 17 o 18, invece di 16.
riformato il

2
Ho appena testato SQL Server 2012 e RAISERRORdi fatto non terminerà l'esecuzione se la gravità è impostata su 17 o 18, invece di 16.
Ian Boyd

1
Il motivo è chiaramente spiegato da Erland Sommarskog (MVP di SQL Server dal 2001) nella parte 2 della sua eccellente serie Error and Transaction Handling in SQL Server: "Ogni tanto ho la sensazione che SQL Server sia intenzionalmente progettato per essere confondere il più possibile. Quando pianificano una nuova versione si chiedono l'un l'altro cosa possiamo fare questa volta per confondere gli utenti? A volte finiscono un po 'le idee, ma poi qualcuno dice Facciamo qualcosa con la gestione degli errori! "
Ingegnere inverso

@IanBoyd, infatti, anche dopo aver impostato la gravità su 17 o 18 o 19 l'esecuzione non viene interrotta. La cosa più interessante, se guardi nella Messagesscheda vedrai no (X rows affected)o PRINTmessaggi, che direi è una completa bugia !
Gabrielius

Risposte:


48

Questo è By Design TM , come puoi vedere su Connect dalla risposta del team di SQL Server a una domanda simile:

Grazie per il tuo feedback. Per impostazione predefinita, l'opzione impostata XACT_ABORT non influisce sul comportamento dell'istruzione RAISERROR. Prenderemo in considerazione il tuo feedback per modificare questo comportamento per una versione futura di SQL Server.

Sì, questo è un po 'un problema per alcuni che speravano RAISERRORcon una gravità elevata (come 16) sarebbe stato lo stesso di un errore di esecuzione SQL - non lo è.

La tua soluzione alternativa riguarda solo ciò che devi fare e l'utilizzo di una transazione esplicita non ha alcun effetto sul comportamento che desideri modificare.


1
Grazie Filippo. Il collegamento a cui hai fatto riferimento sembra non essere disponibile.
Eric Z Beard,

2
Il link funziona bene, se mai hai bisogno di cercarlo, titolo "Fai lavorare RAISERROR con XACT_ABORT", autore "jorundur", ID: 275308
JohnC

Il collegamento è morto, senza alcuna copia cache di archive.org. È stato perso per sempre nelle sabbie del tempo.
Ian Boyd,

Questa risposta è un buon backup, con un collegamento ai documenti in cui questo comportamento è reso chiaro.
pcdev

25

Se usi un blocco try / catch, un numero di errore raiserror con gravità 11-19 farà saltare l'esecuzione al blocco catch.

Qualsiasi gravità superiore a 16 è un errore di sistema. Per dimostrare il codice seguente imposta un blocco try / catch ed esegue una stored procedure che si presume fallirà:

supponiamo di avere una tabella [dbo]. [Errors] per contenere gli errori presumiamo di avere una procedura memorizzata [dbo]. [AssumeThisFails] che fallirà quando la eseguiremo

-- 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

Usalo RETURNsubito dopo RAISERROR()e non eseguirà più la procedura.


8
Potresti voler chiamare rollback transactionprima di chiamare return.
Mike Christian

1
Probabilmente potresti dover fare qualcosa nel tuo blocco di cattura
sqluser

14

Come sottolineato nei documenti per SET XACT_ABORT, l' THROWistruzione dovrebbe essere usata al posto di RAISERROR.

I due si comportano in modo leggermente diverso . Ma quando XACT_ABORTè impostato su ON, dovresti sempre usare il THROWcomando.


25
Se non hai 2k12 (o superiore quando esce), allora non c'è nessuna istruzione THROW da avere.
Jeff Moden
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.