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?
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.
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
Utilisez RETURN
immédiatement après RAISERROR()
et il n'exécutera pas la procédure plus loin.
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
.