web-dev-qa-db-fra.com

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

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 maître SP 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, alors elle restaurera automatiquement tous les SP du maître SP ou dois-je faire 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
25
user2483342

Étant donné uniquement le code affiché dans la question et en supposant qu'aucun des trois sous-processus n'a de gestion de transaction explicite, alors oui, une erreur dans l'un des trois sous-processus sera détectée et le ROLLBACK dans le bloc CATCH annulera tout le 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 appelez BEGIN 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 the 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 commandes COMMIT émises lorsque @@TRANCOUNT Est à 2 ou au-dessus ne font que réduire, une à la fois, le compteur de transactions.
    • Rien n'est jamais engagé jusqu'à ce qu'un COMMIT soit émis lorsque le @@TRANCOUNT Est à 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ée.

    • Les points de sauvegarde sont créés/marqués via la commande SAVE TRAN {save_point_name}
    • Les points de sauvegarde marquent le début ​​du sous-ensemble de travail qui peut être annulé 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.
    • Enregistrer des points peut ​​être imbriqué.
    • 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 l'appel le plus récent ​​à 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ù le "nidification" ").
    • La restauration d'un point de sauvegarde n'a pas d'effet sur le nombre/niveau de transactions
    • Tout travail effectué avant le SAVE TRAN Initial ne peut pas être annulé, sauf en émettant un ROLLBACK complet de la transaction entière.
    • Juste pour être clair: émettre un COMMIT lorsque @@TRANCOUNT Est à 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 COMMIT, est ignorée et n'existe que pour la lisibilité.

  • Un ROLLBACK émis sans nom annulera toujours TOUTES les transactions.

  • Un ROLLBACK émis avec un nom doit correspondre à:

    • La première transaction, en supposant qu'elle a été nommée:
      En supposant qu'aucun SAVE TRAN N'a été appelé avec le même nom de transaction, cela annulera TOUTES les transactions.
    • Un "point de sauvegarde" (décrit ci-dessus):
      Ce comportement "annulera" toutes les modifications apportées depuis l'appel de le plus récent ​​SAVE TRAN {save_point_name}.
    • Si la première transaction était a) nommée et b) a reçu des commandes SAVE TRAN 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 B Une fois: il annulera "DML Query 4". @@TRANCOUNT Est toujours 2.
      • ROLLBACK TRAN B Deux fois: cela annulera "DML Query 4" puis une erreur car il n'y a pas de point de sauvegarde correspondant pour "B". @@TRANCOUNT Est toujours 2.
      • ROLLBACK TRAN A Une fois: il annulera "DML Query 4" et "DML Query 3". @@TRANCOUNT Est toujours 2.
      • ROLLBACK TRAN A Deux fois: il annulera "DML Query 4", "DML Query 3" et "DML Query 2". @@TRANCOUNT Est toujours 2.
      • ROLLBACK TRAN A Trois fois: il annulera "DML Query 4", "DML Query 3" et "DML Query 2". Ensuite, il annulera toute la transaction (il ne restait plus que "DML Query 1"). @@TRANCOUNT Est désormais à 0.
      • COMMIT une fois: @@TRANCOUNT descend à 1.
      • COMMIT une fois puis ROLLBACK TRAN B une fois: @@TRANCOUNT descend à 1. Ensuite, il annulera "DML Query 4" (prouvant que COMMIT n'a rien fait). @@TRANCOUNT Est 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 de T-SQL Identifiants
  • 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 des requêtes uniques ne sont pas nécessaires, sauf s'il peut y avoir une raison programmatique de faire un 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 de @@TRANCOUNT Étant la même que lors de son appel. Vous ne pouvez donc pas:

    • Démarrez un BEGIN TRAN Dans le proc sans le valider, en attendant de le valider dans le processus appelant/parent.
    • Vous ne pouvez pas émettre un ROLLBACK si une transaction explicite a été lancée avant l'appel du proc car elle renverra @@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 différent 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 gère depuis plusieurs années maintenant qui semble bien fonctionner est de ne faire que BEGIN/COMMIT/ROLLBACK au niveau le plus externe. Les appels de 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).

  • En haut de chaque proc, DECLARE @InNestedTransaction BIT;
  • Au lieu du 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 du simple COMMIT, faites:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
    
  • Au lieu du 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é lancé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 de transaction dans la construction TRY...CATCH, Veuillez consulter ma réponse à la question DBA.SE suivante: Sommes-nous tenus de gérer la transaction 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 ROLLBACK vous-même. Cependant, parfois les choses se compliquent, comme dans le cas d'erreurs d'abandon par lots, ou lors de l'utilisation de 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 interceptées en utilisant TRY...CATCH, Il y en a deux qui ne peuvent pas être intercepté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 ON Pour annuler correctement la transaction.

    SET XACT_ABORT ON oblige SQL Server à immédiatement ​​annuler toute transaction (si une est active) et ​​abandonner le lot si tout une erreur se produit. Ce paramètre existait avant SQL Server 2005, qui a introduit la construction TRY...CATCH. Pour la plupart, TRY...CATCH Gère la plupart des situations et rend donc la plupart du temps obsolète le besoin de XACT_ABORT ON. Cependant, lorsque vous utilisez OPENQUERY (et peut-être un autre scénario dont je ne me souviens pas pour le moment), vous devrez toujours utiliser SET XACT_ABORT ON;.

  • À l'intérieur d'un déclencheur, XACT_ABORT Est implicitement défini sur ON. Cela provoque une erreur any 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 construction TRY...CATCH, Introduite dans SQL Server 2005, fournit un moyen de gérer presque toutes les situations, une amélioration bienvenue par rapport au test de @@ERROR Après chaque instruction, ce qui n'a pas beaucoup aidé à l'abandon de lots les erreurs.

    TRY...CATCH A cependant introduit un nouvel "état". Lorsque not ​​utilisant la construction TRY...CATCH, Si vous avez une transaction active et qu'une erreur se produit, plusieurs chemins peuvent être empruntés:

    • XACT_ABORT OFF Et erreur d'abandon de l'instruction: la transaction est toujours active et le traitement se poursuit avec la prochaine instruction, le cas échéant.
    • XACT_ABORT OFF Et la plupart des erreurs d'abandon de lot: la transaction est toujours active et le traitement se poursuit avec le prochain lot, le cas échéant.
    • XACT_ABORT OFF Et certaines erreurs d'abandon de lot: la transaction est annulée et le traitement se poursuit avec le prochain lot, le cas échéant.
    • XACT_ABORT ON Et any erreur: la transaction est annulée et le traitement se poursuit avec le batch suivant, le cas échéant.


    TOUTEFOIS, lors de l'utilisation de TRY...CATCH, Les erreurs d'abandon de lot n'interrompent pas le lot, mais transfèrent le contrôle au bloc CATCH. Lorsque XACT_ABORT Est OFF, la transaction sera toujours active la grande majorité du temps, et vous devrez COMMIT, ou très probablement, ROLLBACK . Mais lorsque vous rencontrez certaines erreurs d'abandon de lot (comme avec OPENQUERY), ou lorsque XACT_ABORT Est ON, la transaction sera dans un nouvel état, "non engageable". Dans cet état, vous ne pouvez pas COMMIT, ni effectuer aucune opération DML. Tout ce que vous pouvez faire est des instructions ROLLBACK et SELECT. Cependant, dans cet état "incontrôlable", la transaction a été annulée en cas d'erreur, et l'émission de ROLLBACK n'est qu'une formalité, mais celle-ci 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 bloc CATCH 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, donc il semble que les tests pour @@TRANCOUNT > 0 Et XACT_STATE() <> 0 sont équivalents. D'un autre côté, lorsque XACT_ABORT Est OFF et qu'une Transaction est active, il est possible d'avoir un état de 1 Ou -1 Dans le bloc CATCH, qui permet la possibilité d'émettre COMMIT au lieu de ROLLBACK (bien que je ne puisse pas penser à un cas où quelqu'un voudrait COMMIT si la Transaction est validable). Plus d'informations et de recherches sur l'utilisation de XACT_STATE() dans un bloc CATCH avec XACT_ABORT ON Se trouvent dans ma réponse à la question DBA.SE suivante: Dans quels cas une transaction peut ê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 avec XACT_STATE() qui le fait renvoyer faussement 1 Dans 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 des BEGIN et END autour de chaque EXEC appel
58
Solomon Rutzky

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

2
aasim.abdullah