web-dev-qa-db-fra.com

Procédures stockées imbriquées contenant le modèle TRY CATCH ROLLBACK?

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)

53
David

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:

  • Chaque proc stocké doit être conforme au même modèle

Résumé

  • Donc, ne créez pas plus de TXN que nécessaire

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.

105
gbn

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 )

10
Aaron Bertrand

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)
1
Amanda

- 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
0
Ben Tennen