it-swarm.it

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

Sono rimasto sorpreso da qualcosa in TSQL. Pensavo che se xact_abort fosse attivo, chiamando qualcosa del genere

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

interromperà l'esecuzione della procedura memorizzata (o di qualsiasi batch).

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

Questa è la mia soluzione alternativa (che è comunque 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 ripristinata.

Ciò significa che devo utilizzare una transazione esplicita?

84
Eric Z Beard

Questo è di designTM, come puoi vedere su Connetti dalla risposta del team SQL Server a una domanda simile:

Grazie per il tuo feedback. In base alla progettazione, l'opzione set XACT_ABORT non influisce sul comportamento dell'istruzione RAISERROR. Considereremo il tuo feedback per modificare questo comportamento per una versione futura di SQL Server.

Sì, questo è un po 'un problema per alcuni che speravano RAISERROR con un livello di gravità elevato (come 16) sarebbe lo stesso di un errore di esecuzione SQL - non lo è.

La soluzione alternativa è esattamente ciò che è necessario fare e l'utilizzo di una transazione esplicita non ha alcun effetto sul comportamento che si desidera modificare.

45
Philip Rieck

Se si utilizza un blocco try/catch, un numero di errore raiserror con gravità 11-19 causerà l'esecuzione del salto nel blocco catch.

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

supponiamo di avere una tabella [dbo]. [Errori] per contenere errori supponiamo di avere una procedura memorizzata [dbo]. [AssumeThisFails] che fallirà quando la eseguiamo

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

Utilizzare RETURN immediatamente dopo RAISERROR() e non eseguirà ulteriormente la procedura.

22
piyush

Come sottolineato su MSDN l'istruzione THROW dovrebbe essere usata al posto di RAISERROR.

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

13
Möoz