Je suis intéressé par les effets secondaires et les problèmes potentiels du schéma suivant:
CREATE PROCEDURE [Name]
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
[...Perform work, call nested procedures...]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
END
Autant que je sache, ce modèle est valable lorsqu'il est utilisé avec une procédure unique - la procédure remplit toutes ses instructions sans erreur, ou annule toutes les actions et signale l'erreur.
Cependant, lorsqu'une procédure stockée appelle une autre procédure stockée pour effectuer une sous-unité de travail (étant entendu que la procédure plus petite est parfois appelée seule), je vois un problème lié aux annulations: un message d'information (niveau 16). est émis en indiquant The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
. Je suppose que cela est dû au fait que la restauration dans la sous-procédure annule toujours la transaction la plus externe, pas seulement la transaction démarrée dans la sous-procédure.
Je souhaite que tout soit annulé et annulé en cas d'erreur (et que l'erreur soit signalée au client sous forme d'erreur SQL). Je ne suis tout simplement pas sûr de tous les effets secondaires des couches externes qui tentent d'annuler une transaction. cela a déjà été annulé. Peut-être une vérification de @@TRANCOUNT
avant de faire une restauration à chaque couche TRY CATCH?
Enfin, il y a la fin du client (Linq2SQL), qui a sa propre couche de transaction:
try
{
var context = new MyDataContext();
using (var transaction = new TransactionScope())
{
// Some Linq stuff
context.SubmitChanges();
context.MyStoredProcedure();
transactionComplete();
}
}
catch
{
// An error occured!
}
Dans le cas où une procédure stockée, "MySubProcedure", appelée inside MyStoredProcedure génère une erreur, puis-je être sûr que tout ce qui a déjà été fait dans MyStoredProcedure sera annulé, toutes les opérations Linq effectuées par SubmitChanges seront annulées, et enfin que l'erreur sera enregistrée? Ou que dois-je changer dans mon modèle pour que l'opération soit entièrement atomique, tout en permettant aux parties enfants d'être utilisées individuellement (c'est-à-dire que les sous-procédures doivent toujours avoir la même protection atomique)
Ceci est notre modèle (enregistrement des erreurs supprimé)
Ceci est conçu pour gérer
Explications:
tous les TXN begin et commit/rollbacks doivent être appariés pour que @@TRANCOUNT
soit identique à l'entrée et à la sortie
inadéquation de @@TRANCOUNT
cause l'erreur 266 parce que
BEGIN TRAN
incrémente @@TRANCOUNT
COMMIT
décrémente @@TRANCOUNT
ROLLBACK
renvoie @@TRANCOUNT
à zéro
Vous ne pouvez pas décrémenter @@TRANCOUNT
pour la portée actuelle
C’est ce que vous pensez être la "transaction intérieure"
SET XACT_ABORT ON
supprime l'erreur 266 causée par une incompatibilité @@TRANCOUNT
Et traite également des problèmes comme celui-ci "Délai de transaction SQL Server" sur dba.se
Cela permet d'utiliser des TXN côté client (tels que LINQ) Une procédure stockée peut faire partie d'une transaction distribuée ou XA, ou simplement d'une procédure initiée dans le code client (par exemple .net TransactionScope).
Utilisation:
Résumé
Le code
CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE @starttrancount int
BEGIN TRY
SELECT @starttrancount = @@TRANCOUNT
IF @starttrancount = 0
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
IF @starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND @starttrancount = 0
ROLLBACK TRANSACTION;
THROW;
--before SQL Server 2012 use
--RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO
Remarques:
La vérification d'annulation est en réalité redondante à cause de SET XACT_ABORT ON
. Cependant, ça me fait me sentir mieux, ça a l'air bizarre sans, et ça permet des situations où vous ne le voulez pas
Remus Rusanu a un Shell similaire qui utilise des points de sauvegarde. Je préfère un appel de base de données atomique et n'utilise pas de mises à jour partielles comme leur article.
Je ne suis pas un gars de Linq (ni Erland), mais il a écrit les bibles absolues sur la gestion des erreurs. En dehors des complications que Linq pourrait ajouter à votre problème, vous devez répondre à toutes vos autres questions ici:
http://www.sommarskog.se/error_handling/Part1.html
(Ancien lien: http://www.sommarskog.se/error_handling_2005.html )
Pour résoudre le problème du renvoi du numéro d'erreur et du numéro de ligne mentionnés par @AlexKuznetsov, on peut générer l'erreur en tant que telle:
DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT
DECLARE @ErrorLine INT
DECLARE @ErrorNumber INT
SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
- La méthode @Amanda ci-dessus ne renvoie pas le numéro d'erreur correct
DECLARE
@ErrorMessage nvarchar(4000),
@ErrorSeverity int,
@ErrorState int,
@ErrorLine int,
@ErrorNumber int
BEGIN TRY
SELECT 1/0; -- CATCH me
END TRY
BEGIN CATCH
DECLARE @err int = @@ERROR
PRINT @err -- 8134, divide by zero
PRINT ERROR_NUMBER() -- 8134
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()
-- error number = 50000 :(
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
END CATCH
-- error number = 8134
SELECT 1/0