web-dev-qa-db-fra.com

Pourquoi Sql Server continue-t-il à s'exécuter après raiserror lorsque xact_abort est activé?

Je viens d'être surpris par quelque chose dans TSQL. Je pensais que si xact_abort était activé, appeler quelque chose comme

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

arrêterait l'exécution de la procédure stockée (ou de tout lot).

Mais mon message d'erreur ADO.NET vient de prouver le contraire. J'ai reçu le message d'erreur raiserror dans le message d'exception, ainsi que la prochaine chose qui s'est cassée après cela.

C'est ma solution de contournement (qui est mon habitude de toute façon), mais il ne semble pas que cela devrait être nécessaire:

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

Les docs disent ceci:

Lorsque SET XACT_ABORT est activé, si une instruction Transact-SQL génère une erreur d'exécution, la transaction entière est terminée et annulée.

Est-ce à dire que je dois utiliser une transaction explicite?

84
Eric Z Beard

C'est par conceptionTM, comme vous pouvez le voir sur Connect par la réponse de l'équipe SQL Server à une question similaire:

Merci pour votre avis. De par sa conception, l'option set XACT_ABORT n'a pas d'impact sur le comportement de l'instruction RAISERROR. Nous prendrons en compte vos commentaires pour modifier ce comportement pour une future version de SQL Server.

Oui, c'est un peu un problème pour certains qui espéraient que RAISERROR avec une gravité élevée (comme 16) serait la même chose qu'une erreur d'exécution SQL - ce n'est pas le cas.

Votre solution de contournement est à peu près ce que vous devez faire, et l'utilisation d'une transaction explicite n'a aucun effet sur le comportement que vous souhaitez modifier.

45
Philip Rieck

Si vous utilisez un bloc try/catch, un numéro d'erreur de relance avec la gravité 11-19 entraînera l'exécution pour sauter au bloc catch.

Toute gravité supérieure à 16 est une erreur système. Pour illustrer le code suivant, configurez un bloc try/catch et exécute une procédure stockée que nous supposons échouer:

supposons que nous avons une table [dbo]. [Erreurs] pour contenir les erreurs supposons que nous avons une procédure stockée [dbo]. [AssumeThisFails] qui échouera lorsque nous l'exécuterons

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

Utilisez RETURN immédiatement après RAISERROR() et il n'exécutera pas la procédure plus loin.

22
piyush

Comme indiqué sur MSDN l'instruction THROW doit être utilisée à la place de RAISERROR.

Les deux se comportent légèrement différemment . Mais quand XACT_ABORT est défini sur ON, vous devez toujours utiliser la commande THROW.

13
Möoz