Comment restaurer lorsque 3 procédures stockées sont démarrées à partir d'une procédure stockée


23

J'ai une procédure stockée qui n'exécute que 3 procédures stockées à l'intérieur. J'utilise seulement 1 paramètre pour stocker si le SP maître est réussi.

Si la première procédure stockée fonctionne correctement dans la procédure stockée principale, mais que la 2ème procédure stockée échoue, est-ce qu'elle annulera automatiquement tous les SP dans le SP maître ou dois-je effectuer une commande?

Voici ma procédure:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO

Si spNewBilling3renvoie une erreur, mais que vous ne souhaitez pas revenir en arrière spNewBilling2ou spNewBilling1, supprimez simplement [begin|rollback|commit] transaction createSavebillinginvoicede spSavesomename.
Mike

Réponses:


56

Étant donné uniquement le code indiqué dans la question, et en supposant qu'aucun des trois sous-procs n'a de gestion de transaction explicite, alors oui, une erreur dans l'un des trois sous-procs sera détectée et ROLLBACKle CATCHbloc bloquera tous du travail.

MAIS voici quelques points à noter sur les transactions (au moins dans SQL Server):

  • Il n'y a qu'une seule transaction réelle (la première), peu importe le nombre de fois que vous appelezBEGIN TRAN

    • Vous pouvez nommer une transaction (comme vous l'avez fait ici) et ce nom apparaîtra dans les journaux, mais la dénomination n'a de sens que pour la première transaction / la plus externe (car encore une fois, la première est la transaction).
    • Chaque fois que vous appelez BEGIN TRAN, qu'il soit nommé ou non, le compteur de transactions est incrémenté de 1.
    • Vous pouvez voir le niveau actuel en faisant SELECT @@TRANCOUNT;
    • Toutes les COMMITcommandes émises quand @@TRANCOUNTest à 2 ou plus ne font que réduire, un à la fois, le compteur de transactions.
    • Rien n'est jamais commis jusqu'à ce qu'un COMMITsoit émis lorsque le @@TRANCOUNTest à1
    • Juste au cas où les informations ci-dessus n'indiqueraient pas clairement: quel que soit le niveau de transaction, il n'y a pas d'imbrication réelle des transactions.
  • Les points de sauvegarde permettent de créer un sous-ensemble de travail dans la transaction qui peut être annulé.

    • Les points de sauvegarde sont créés / marqués via la SAVE TRAN {save_point_name}commande
    • Les points de sauvegarde marquent le début du sous-ensemble de travaux qui peuvent être annulés sans annuler la transaction entière.
    • Les noms de point d'enregistrement n'ont pas besoin d'être uniques, mais l'utilisation du même nom plus d'une fois crée toujours des points d'enregistrement distincts.
    • Les points de sauvegarde peuvent être imbriqués.
    • Les points de sauvegarde ne peuvent pas être validés.
    • Les points de sauvegarde peuvent être annulés via ROLLBACK {save_point_name}. (plus d'informations ci-dessous)
    • La restauration d'un point de sauvegarde annulera tout travail survenu après le dernier appel à SAVE TRAN {save_point_name}, y compris tous les points de sauvegarde créés après la création de celui qui a été annulé (d'où l'imbrication).
    • La restauration d'un point de sauvegarde n'a pas d'effet sur le nombre / niveau de transactions
    • Tout travail effectué avant l'initiale SAVE TRANne peut être annulé, sauf en émettant un plein ROLLBACKde la transaction entière.
    • Juste pour être clair: l'émission d'un COMMITquand @@TRANCOUNTest à 2 ou plus, n'a aucun effet sur les points de sauvegarde (car encore une fois, les niveaux de transaction supérieurs à 1 n'existent pas en dehors de ce compteur).
  • Vous ne pouvez pas valider des transactions nommées spécifiques. La transaction "nom", si elle est fournie avec le COMMIT, est ignorée et n'existe que pour la lisibilité.

  • Une ROLLBACKémission sans nom annulera toujours TOUTES les transactions.

  • Un titre ROLLBACKdélivré doit correspondre à:

    • La première transaction, en supposant qu'elle a été nommée: en
      supposant qu'aucun SAVE TRANn'a été appelé avec le même nom de transaction, cela annulera TOUTES les transactions.
    • Un "point d'enregistrement" (décrit ci-dessus):
      ce comportement "annulera" toutes les modifications apportées depuis l' appel du plus récent SAVE TRAN {save_point_name} .
    • Si la première transaction était a) nommée et b) a reçu des SAVE TRANcommandes avec son nom, alors chaque ROLLBACK de ce nom de transaction annulera chaque point de sauvegarde jusqu'à ce qu'il n'en reste plus. Après cela, un ROLLBACK émis de ce nom annulera TOUTES les transactions.
    • Par exemple, supposons que les commandes suivantes ont été exécutées dans l'ordre indiqué:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4

      Maintenant, si vous émettez (chacun des scénarios suivants est indépendant les uns des autres):

      • ROLLBACK TRAN Bune fois: il annulera "DML Query 4". @@TRANCOUNTest toujours 2.
      • ROLLBACK TRAN Bdeux fois: cela annulera "DML Query 4" puis une erreur car il n'y a pas de point de sauvegarde correspondant pour "B". @@TRANCOUNTest toujours 2.
      • ROLLBACK TRAN Aune fois: il annulera "DML Query 4" et "DML Query 3". @@TRANCOUNTest toujours 2.
      • ROLLBACK TRAN Adeux fois: il annulera "DML Query 4", "DML Query 3" et "DML Query 2". @@TRANCOUNTest toujours 2.
      • ROLLBACK TRAN Atrois fois: il annulera "DML Query 4", "DML Query 3" et "DML Query 2". Ensuite, il annulera l'intégralité de la transaction (il ne restait plus que "DML Query 1"). @@TRANCOUNTest maintenant 0.
      • COMMITune fois: @@TRANCOUNTdescend à 1.
      • COMMITune fois et puis ROLLBACK TRAN Bune fois: @@TRANCOUNTdescend à 1. Ensuite, il annulera "DML Query 4" (prouvant que COMMIT n'a rien fait). @@TRANCOUNTest toujours 1.
  • Noms des transactions et noms des points d'enregistrement:

    • peut contenir jusqu'à 32 caractères
    • sont traités comme ayant un classement binaire (non sensible à la casse comme la documentation l'indique actuellement), quels que soient les classements au niveau de l'instance ou au niveau de la base de données.
    • Pour plus de détails, veuillez consulter la section Noms des transactions de l' article suivant: Qu'y a-t-il dans un nom?: Dans le monde loufoque des identifiants T-SQL
  • Une procédure stockée n'est pas, en soi, une transaction implicite. Chaque requête, si aucune transaction explicite n'a été démarrée, est une transaction implicite. C'est pourquoi les transactions explicites autour de requêtes uniques ne sont pas nécessaires, sauf s'il peut y avoir une raison programmatique de le faire ROLLBACK, sinon toute erreur dans la requête est une restauration automatique de cette requête.

  • Lors de l'appel d'une procédure stockée, elle doit quitter avec la valeur d' @@TRANCOUNTêtre la même que lorsqu'elle a été appelée. Vous ne pouvez donc pas:

    • Démarrez un BEGIN TRANdans le proc sans le valider, en attendant de le valider dans le processus appelant / parent.
    • Vous ne pouvez pas émettre de a ROLLBACKsi une transaction explicite a été lancée avant l'appel du proc car elle reviendra @@TRANCOUNTà 0.

    Si vous quittez une procédure stockée avec un nombre de transactions supérieur ou inférieur à celui où elle a démarré, vous obtiendrez une erreur similaire à:

    Msg 266, niveau 16, état 2, procédure YourProcName, ligne 0
    Le nombre de transactions après EXECUTE indique un nombre non concordant d'instructions BEGIN et COMMIT. Nombre précédent = X, nombre actuel = Y.

  • Les variables de table, tout comme les variables régulières, ne sont pas liées par des transactions.


En ce qui concerne la gestion des transactions dans les procs qui peut être appelée indépendamment (et donc avoir besoin de la gestion des transactions) ou appeler à partir d'autres procs (donc pas besoin de la gestion des transactions): cela peut être accompli de deux manières différentes.

La façon dont je le manipule depuis plusieurs années maintenant qui semble bien fonctionner est de n'utiliser que BEGIN/ COMMIT/ ROLLBACKsur la couche la plus externe. Les appels sous-proc ignorent simplement les commandes de transaction. J'ai décrit ci-dessous ce que je mets dans chaque proc (enfin, chacun qui nécessite une gestion de transaction).

  • Au sommet de chaque proc, DECLARE @InNestedTransaction BIT;
  • Au lieu de simple BEGIN TRAN, faites:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • Au lieu de simple COMMIT, faites:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Au lieu de simple ROLLBACK, faites:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Cette méthode doit fonctionner de la même manière, que la transaction ait été démarrée dans SQL Server ou qu'elle ait été lancée au niveau de la couche d'application.

Pour le modèle complet de cette gestion des transactions dans la TRY...CATCHconstruction, veuillez consulter ma réponse à la question DBA.SE suivante: sommes-nous tenus de gérer les transactions en code C # ainsi qu'en procédure stockée .


Au-delà des "bases", il y a quelques nuances supplémentaires à prendre en compte:

  • Par défaut, les transactions ne sont, la plupart du temps, pas automatiquement annulées / annulées lorsqu'une erreur se produit. Ce n'est généralement pas un problème tant que vous gérez correctement les erreurs et appelez- ROLLBACKvous. Cependant, parfois les choses se compliquent, comme dans le cas d'erreurs d'abandon par lots, ou lors de l'utilisation OPENQUERY(ou des serveurs liés en général) et une erreur se produit sur le système distant. Alors que la plupart des erreurs peuvent être piégées en utilisant TRY...CATCH, il y en a deux qui ne peuvent pas être piégées de cette façon (cependant, je ne me souviens pas lesquelles pour le moment - recherche). Dans ces cas, vous devez utiliser SET XACT_ABORT ONpour annuler correctement la transaction.

    SET XACT_ABORT ON fait que SQL Server annule immédiatement toute transaction (si une est active) et abandonne le lot si une erreur se produit. Ce paramètre existait avant SQL Server 2005, qui a introduit la TRY...CATCHconstruction. Pour la plupart, TRY...CATCHgère la plupart des situations et rend donc la plupart du temps obsolète XACT_ABORT ON. Cependant, lors de l'utilisation OPENQUERY(et éventuellement d'un autre scénario dont je ne me souviens pas pour le moment), vous devrez toujours l'utiliser SET XACT_ABORT ON;.

  • À l'intérieur d'un déclencheur, XACT_ABORTest implicitement défini sur ON. Cela provoque toute erreur dans le déclencheur pour annuler l'intégralité de l'instruction DML qui a déclenché le déclencheur.

  • Vous devez toujours avoir une gestion des erreurs appropriée, en particulier lorsque vous utilisez des transactions. La TRY...CATCHconstruction, introduite dans SQL Server 2005, fournit un moyen de gérer presque toutes les situations, une amélioration bienvenue par rapport aux tests @@ERRORaprès chaque instruction, ce qui n'a pas beaucoup aidé avec les erreurs d'abandon de lot.

    TRY...CATCHintroduit un nouvel «État». Lorsque vous n'utilisez pas la TRY...CATCHconstruction, si vous avez une transaction active et qu'une erreur se produit, plusieurs chemins peuvent être empruntés:

    • XACT_ABORT OFFet erreur d'abandon de l'instruction: la transaction est toujours active et le traitement se poursuit avec l' instruction suivante , le cas échéant.
    • XACT_ABORT OFFet la plupart des erreurs d'abandon de lot: la transaction est toujours active et le traitement se poursuit avec le lot suivant , le cas échéant.
    • XACT_ABORT OFFet certaines erreurs d'abandon de lot: la transaction est annulée et le traitement se poursuit avec le lot suivant , le cas échéant.
    • XACT_ABORT ONet toute erreur: la transaction est annulée et le traitement se poursuit avec le lot suivant , le cas échéant.


    TOUTEFOIS, lors de l'utilisation TRY...CATCH, les erreurs d'abandon de lot n'interrompent pas le lot, mais transfèrent plutôt le contrôle au CATCHbloc. Lorsque XACT_ABORTc'est le cas OFF, la transaction sera toujours active la grande majorité du temps, et vous devrez COMMIT, ou très probablement, le faire ROLLBACK. Mais lorsque vous rencontrez certaines erreurs d'abandon de lot (comme avec OPENQUERY), ou quand XACT_ABORTc'est le cas ON, la transaction sera dans un nouvel état, "non engageable". Dans cet état, vous ne pouvez COMMITni ne pouvez effectuer aucune opération DML. Tout ce que vous pouvez faire est ROLLBACKet SELECTdéclarations. Cependant, dans cet état "incontrôlable", la transaction a été annulée lors de l'erreur qui s'est produite, et l'émission de la ROLLBACKest juste une formalité, mais celle qui doit être effectuée.

    Une fonction, XACT_STATE , peut être utilisée pour déterminer si une transaction est active, non engageable ou n'existe pas. Il est recommandé (par certains, au moins) de vérifier cette fonction dans le CATCHbloc pour déterminer si le résultat est -1(c'est-à-dire non engageable) au lieu de tester si @@TRANCOUNT > 0. Mais avec XACT_ABORT ON, cela devrait être le seul état possible, il semble donc que les tests pour @@TRANCOUNT > 0et XACT_STATE() <> 0soient équivalents. D'un autre côté, quand XACT_ABORTest OFFet qu'il y a une Transaction active, alors il est possible d'avoir un état de 1ou -1dans le CATCHbloc, ce qui permet la possibilité d'émettre à la COMMITplace de ROLLBACK(bien que, je ne puisse pas penser à un cas où quelqu'un voudraitCOMMITsi la transaction est validable). Vous trouverez plus d'informations et de recherches sur l'utilisation XACT_STATE()dans un CATCHbloc avec XACT_ABORT ONdans ma réponse à la question DBA.SE suivante: dans quels cas une transaction peut-elle être validée depuis l'intérieur du bloc CATCH lorsque XACT_ABORT est défini sur ON? . Veuillez noter qu'il existe un bogue mineur XACT_STATE()qui le fait renvoyer à tort 1dans certains scénarios: XACT_STATE () renvoie 1 lorsqu'il est utilisé dans SELECT avec certaines variables système mais sans clause FROM


Remarques sur le code d'origine:

  • Vous pouvez supprimer le nom donné à la transaction car cela n'aide en rien.
  • Vous n'avez pas besoin du BEGINet ENDautour de chaque EXECappel

2
C'est une très bonne, bonne réponse.
McNets

1
Wow, c'est une réponse complète! Merci! BTW dos la page suivante traite des erreurs auxquelles vous faites allusion qui ne sont pas piégées par Try ... Catch? (Sous la rubrique "Erreurs non affectées par une construction TRY… CATCH"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba

1
@jrdevdba Merci :-). Et bienvenue. En ce qui concerne les erreurs non piégées, je voulais à peu près les deux: Compile errors, such as syntax errors, that prevent a batch from runninget Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Mais ils ne se produisent pas très souvent, et lorsque vous trouvez une telle situation, corrigez-la (s'il s'agit d'un bogue dans le code) ou placez-la dans un sous-processus ( EXECou sp_executesql) afin qu'elle TRY...CATCHpuisse la piéger.
Solomon Rutzky

2

Oui, si en raison d'une erreur de code d'annulation dans l'instruction catch de votre procédure stockée principale, elle s'exécutera, elle annulera toutes les opérations effectuées par n'importe quelle instruction directe ou via l'une de vos procédures stockées imbriquées.

Même si vous n'avez appliqué aucune transaction explicite dans vos procédures stockées imbriquées, ces procédures stockées utiliseront une transaction implicite et seront validées à la fin MAIS soit vous avez effectué une transaction explicite ou implicite dans des procédures stockées imbriquées, le moteur SQL Server l'ignorera et le fera annule toutes les actions de ces procédures stockées imbriquées si la procédure stockée principale échoue et que la transaction est annulée.

Chaque fois que la transaction est validée ou annulée en fonction de l'action effectuée à la fin de la transaction la plus externe. Si la transaction externe est validée, les transactions internes imbriquées sont également validées. Si la transaction externe est annulée, toutes les transactions internes sont également annulées, que les transactions internes aient été ou non validées individuellement.

Pour référence http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.