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?

13

J'ai lu MSDN sur TRY...CATCHet XACT_STATE.

Il a l'exemple suivant qui utilise XACT_STATEdans le CATCHbloc d'une TRY…CATCHconstruction pour déterminer s'il faut valider ou annuler une transaction:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Ce que je ne comprends pas, c'est pourquoi devrais-je m'en soucier et vérifier quels XACT_STATEretours?

Veuillez noter que l'indicateur XACT_ABORTest défini ONdans l'exemple.

S'il y a une erreur suffisamment grave à l'intérieur du TRY bloc, le contrôle passe CATCH. Donc, si je suis à l'intérieur CATCH, je sais que la transaction a eu un problème et la seule chose sensée à faire dans ce cas est de l'annuler, n'est-ce pas?

Mais, cet exemple de MSDN implique qu'il peut y avoir des cas où le contrôle est passé CATCHet qu'il est toujours logique de valider la transaction. Quelqu'un pourrait-il fournir un exemple pratique quand cela peut arriver, quand cela a du sens?

Je ne vois pas dans quels cas le contrôle peut être passé à l'intérieur CATCHavec une transaction qui peut être validée quand XACT_ABORTest définie surON .

L'article MSDN sur SET XACT_ABORTa un exemple lorsque certaines instructions à l'intérieur d'une transaction s'exécutent avec succès et certaines échouent lorsque XACT_ABORTest défini sur OFF, je comprends cela. Mais, SET XACT_ABORT ONcomment peut-il arriver queXACT_STATE() renvoie 1 à l'intérieur du CATCHbloc?

Au départ, j'aurais écrit ce code comme ceci:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

En tenant compte d'une réponse de Max Vernon, j'écrirais le code comme ceci. Il a montré qu'il est logique de vérifier s'il y a une transaction active avant de tenter de le faire ROLLBACK. Pourtant, avec SET XACT_ABORT ONle CATCHbloc, il peut y avoir une transaction vouée à l'échec ou aucune transaction. Donc, en tout cas, il n'y a rien à faire COMMIT. Ai-je tort?

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO
Vladimir Baranov
la source

Réponses:

8

Il s'avère que la transaction ne peut pas être validée depuis l'intérieur du CATCHbloc si XACT_ABORTest définie sur ON.

L'exemple de MSDN est quelque peu trompeur, car la vérification implique que XACT_STATEpeut retourner 1 dans certains cas et il peut être possible de COMMITla transaction.

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Ce n'est pas vrai, XACT_STATEne retournera jamais 1 à l'intérieur du CATCHbloc si XACT_ABORTest défini sur ON.

Il semble que l'exemple de code MSDN était destiné à illustrer principalement l'utilisation de la XACT_STATE()fonction quel que soit le XACT_ABORTparamètre. L'exemple de code semble suffisamment générique pour fonctionner avec les deux XACT_ABORTensembles de ONet OFF. C'est juste qu'avec XACT_ABORT = ONle chèqueIF (XACT_STATE()) = 1 devient inutile.


Il y a un très bon ensemble détaillé d'articles sur la gestion des erreurs et des transactions dans SQL Server par Erland Sommarskog. Dans la partie 2 - Classification des erreurs, il présente un tableau complet qui rassemble toutes les classes d'erreurs et comment elles sont gérées par SQL Server et comment TRY ... CATCHet XACT_ABORTmodifie le comportement.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

La dernière colonne du tableau répond à la question. AvecTRY-CATCH et avec XACT_ABORT ONla transaction est vouée à l'échec dans tous les cas possibles.

Une note en dehors de la portée de la question. Comme le dit Erland, cette cohérence est l'une des raisons de s'orienter XACT_ABORTvers ON:

J'ai déjà donné la recommandation que vos procédures stockées devraient inclure la commande SET XACT_ABORT, NOCOUNT ON. Si vous regardez le tableau ci-dessus, vous voyez qu'avec XACT_ABORTeffet, il y a un niveau de cohérence plus élevé. Par exemple, la transaction est toujours vouée à l'échec. Dans ce qui suit, je vais montrer de nombreux exemples où je mets XACT_ABORTà OFF, de sorte que vous pouvez obtenir une compréhension des raisons pour lesquelles vous devriez éviter ce paramètre par défaut.

Vladimir Baranov
la source
7

J'aborderais cela différemment. XACT_ABORT_ONest un marteau, vous pouvez utiliser une approche plus raffinée, voir Gestion des exceptions et transactions imbriquées :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

Cette approche annulera, si possible, uniquement le travail effectué à l'intérieur du bloc TRY et rétablira l'état avant d'entrer dans le bloc TRY. De cette façon, vous pouvez effectuer un traitement complexe, comme itérer un curseur, sans perdre tout le travail en cas d'erreur. Le seul inconvénient est qu'en utilisant des points de sauvegarde de transaction, vous ne pouvez pas utiliser tout ce qui est incompatible avec les points de sauvegarde, comme les transactions distribuées.

Remus Rusanu
la source
Je vous remercie de votre réponse, mais la question est pas vraiment que nous devrions mettre XACT_ABORTà ONou OFF.
Vladimir Baranov
7

TL; DR / Résumé: Concernant cette partie de la question:

Je ne vois pas dans quels cas le contrôle peut être passé à l'intérieur CATCHavec une transaction qui peut être validée quand XACT_ABORTest définie surON .

J'ai fait pas mal de tests à ce sujet maintenant et je ne trouve aucun cas où XACT_STATE()retourne à l' 1intérieur d'un CATCHbloc quand @@TRANCOUNT > 0 et la propriété de session de XACT_ABORTest ON. Et en fait, selon la page MSDN actuelle pour SET XACT_ABORT :

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.

Cette déclaration semble être en accord avec vos spéculations et mes conclusions.

L'article MSDN sur SET XACT_ABORTcontient un exemple lorsque certaines instructions dans une transaction s'exécutent avec succès et d'autres échouent lorsque XACT_ABORTest défini surOFF

Vrai, mais les instructions de cet exemple ne se trouvent pas dans un TRYbloc. Ces mêmes déclarations dans un TRYbloc empêcheraient encore l' exécution de toute déclaration après celle qui a causé l'erreur, mais en supposant que XACT_ABORTest OFF, lorsque le contrôle est passé au CATCHbloc de l'opération est toujours valide physiquement en ce que tous les changements antérieurs ne se produisent sans erreur et peuvent être engagés, si tel est le désir, ou ils peuvent être annulés. D'un autre côté, si XACT_ABORTc'est le cas, ONtoutes les modifications antérieures sont automatiquement annulées, puis vous avez le choix entre: a) émettre unROLLBACKqui est la plupart du temps une simple acceptation de la situation depuis l'opération a déjà été annulée moins remise à zéro @@TRANCOUNTpour 0, ou b) obtenir une erreur. Pas beaucoup de choix, n'est-ce pas?

Un détail peut-être important de ce casse-tête qui n'apparaît pas dans cette documentation SET XACT_ABORTest que cette propriété de session, et même cet exemple de code, existent depuis SQL Server 2000 (la documentation est presque identique entre les versions), antérieure à la TRY...CATCHconstruction qui était introduit dans SQL Server 2005. En consultant à nouveau cette documentation et en regardant l'exemple ( sans le TRY...CATCH), l'utilisation XACT_ABORT ONentraîne une annulation immédiate de la transaction: il n'y a pas d'état de transaction «non engageable» (veuillez noter qu'il n'y a aucune mention à un état de transaction "non engageable" dans cette SET XACT_ABORTdocumentation).

Je pense qu'il est raisonnable de conclure que:

  1. l'introduction de la TRY...CATCHconstruction dans SQL Server 2005 a créé le besoin d'un nouvel état de transaction (c'est-à-dire "non validable") et de la XACT_STATE()fonction pour obtenir ces informations.
  2. archiver XACT_STATE()un CATCHbloc n'a de sens que si les deux conditions suivantes sont vraies:
    1. XACT_ABORTest OFF(sinon XACT_STATE()devrait toujours revenir -1et @@TRANCOUNTserait tout ce dont vous avez besoin)
    2. Vous avez une logique dans le CATCHbloc, ou quelque part dans la chaîne si les appels sont imbriqués, ce qui apporte une modification (une COMMITou même n'importe quelle instruction DML, DDL, etc.) au lieu de faire une ROLLBACK. (il s'agit d'un cas d'utilisation très atypique) ** veuillez consulter la note en bas, dans la section MISE À JOUR 3, concernant une recommandation non officielle de Microsoft de toujours vérifier à la XACT_STATE()place de @@TRANCOUNT, et pourquoi les tests montrent que leur raisonnement ne fonctionne pas.
  3. l'introduction de la TRY...CATCHconstruction dans SQL Server 2005 a, pour la plupart, obsolète la XACT_ABORT ONpropriété de session car elle offre un plus grand degré de contrôle sur la transaction (vous avez au moins la possibilité de le faire COMMIT, à condition que XACT_STATE()cela ne revienne pas -1). Avant SQL Server 2005 , une
    autre façon de voir cela était de fournir un moyen simple et fiable d'arrêter le traitement en cas d'erreur, par rapport à la vérification après chaque instruction.XACT_ABORT ON@@ERROR
  4. Le code exemple de documentation XACT_STATE()est erronée, ou au mieux induire en erreur, en ce sens qu'elle montre la vérification de XACT_STATE() = 1quand XACT_ABORTest ON.

La partie longue ;-)

Oui, cet exemple de code sur MSDN est un peu déroutant (voir aussi: @@ TRANCOUNT (Rollback) vs XACT_STATE ) ;-). Et, je pense que c'est trompeur car cela montre soit quelque chose qui n'a pas de sens (pour la raison que vous demandez: pouvez-vous même avoir une transaction "engageable" dans le CATCHbloc quand XACT_ABORTest ON), ou même si c'est possible, cela se concentre toujours sur une possibilité technique dont peu voudront ou auront besoin, et ignore la raison pour laquelle on est plus susceptible d'en avoir besoin.

S'il y a une erreur suffisamment grave à l'intérieur du bloc TRY, le contrôle passera dans CATCH. Donc, si je suis dans le CATCH, je sais que la transaction a eu un problème et la seule chose sensée à faire dans ce cas est de l'annuler, n'est-ce pas?

Je pense que cela aiderait si nous nous assurions que nous sommes sur la même longueur d'onde concernant ce que l'on entend par certains mots et concepts:

  • "erreur suffisamment grave": Juste pour être clair, ESSAYEZ ... CATCH retiendra la plupart des erreurs. La liste de ce qui ne sera pas intercepté est répertoriée sur cette page MSDN liée, sous la section «Erreurs non affectées par une construction TRY… CATCH».

  • "si je suis dans le CATCH, je sais que la transaction a eu un problème" (em phas est ajouté): si par "transaction" vous voulez dire l' unité logique de travail déterminée par vous en regroupant les instructions dans une transaction explicite, alors très probablement oui. Je pense que la plupart d'entre nous, DB, auraient tendance à être d'accord pour dire que le retour en arrière est "la seule chose sensée à faire", car nous avons probablement une vue similaire sur la façon et la raison pour laquelle nous utilisons des transactions explicites et concevons les étapes qui devraient constituer une unité atomique. de travail.

    Mais, si vous voulez dire les unités de travail réelles qui sont regroupées dans la transaction explicite, alors non, vous ne savez pas que la transaction elle-même a eu un problème. Vous savez seulement une déclaration exécution dans la transaction explicitement définie a soulevé une erreur. Mais il ne s'agit peut-être pas d'une instruction DML ou DDL. Et même s'il s'agissait d'une instruction DML, la transaction elle-même pourrait toujours être validable.

Compte tenu des deux points mentionnés ci-dessus, nous devrions probablement faire une distinction entre les transactions que vous "ne pouvez" pas valider et celles que vous "ne voulez pas" valider.

Lorsque XACT_STATE()renvoie un 1, cela signifie que la transaction est "validable", que vous avez le choix entre COMMITou ROLLBACK. Vous ne voudrez peut-être pas le valider, mais si pour une raison difficile à trouver avec un exemple, pour une raison que vous vouliez, au moins vous pourriez le faire car certaines parties de la transaction se sont terminées avec succès.

Mais lorsque XACT_STATE()renvoie un -1, vous devez vraiment le faire ROLLBACKcar une partie de la transaction est entrée dans un mauvais état. Maintenant, je suis d'accord que si le contrôle a été passé au bloc CATCH, alors il est suffisamment logique de simplement vérifier @@TRANCOUNT, car même si vous pouviez valider la transaction, pourquoi voudriez-vous?

Mais si vous remarquez en haut de l'exemple, le réglage de XACT_ABORT ONchange un peu les choses. Vous pouvez avoir une erreur régulière, après BEGIN TRANcela, passera le contrôle au bloc CATCH quand XACT_ABORTis OFFet XACT_STATE () reviendront 1. MAIS, si XACT_ABORT est ON, alors la transaction est "abandonnée" (c'est-à-dire invalidée) pour toute erreur 'ol puis XACT_STATE()reviendra -1. Dans ce cas, il semble inutile de vérifier XACT_STATE()dans le CATCHbloc car il semble toujours renvoyer un -1quand XACT_ABORTest ON.

Alors à quoi ça sert XACT_STATE()? Quelques indices sont:

  • La page MSDN pour TRY...CATCH, sous la section "Transactions non validables et XACT_STATE", dit:

    Une erreur qui met normalement fin à une transaction en dehors d'un bloc TRY fait passer une transaction dans un état non validable lorsque l'erreur se produit à l'intérieur d'un bloc TRY.

  • La page MSDN pour SET XACT_ABORT , sous la section "Remarques", dit:

    Lorsque SET XACT_ABORT est désactivé, dans certains cas, seule l'instruction Transact-SQL qui a provoqué l'erreur est annulée et la transaction continue son traitement.

    et:

    XACT_ABORT doit être défini sur ON pour les instructions de modification de données dans une transaction implicite ou explicite contre la plupart des fournisseurs OLE DB, y compris SQL Server.

  • La page MSDN pour BEGIN TRANSACTION , dans la section "Remarques", indique:

    La transaction locale lancée par l'instruction BEGIN TRANSACTION est convertie en transaction distribuée si les actions suivantes sont effectuées avant que l'instruction ne soit validée ou annulée:

    • Une instruction INSERT, DELETE ou UPDATE qui fait référence à une table distante sur un serveur lié est exécutée. L'instruction INSERT, UPDATE ou DELETE échoue si le fournisseur OLE DB utilisé pour accéder au serveur lié ne prend pas en charge l'interface ITransactionJoin.

L'utilisation la plus applicable semble se situer dans le contexte des instructions DML du serveur lié. Et je crois que je l'ai rencontré moi-même il y a des années. Je ne me souviens pas de tous les détails, mais cela avait quelque chose à voir avec le serveur distant n'étant pas disponible, et pour une raison quelconque, cette erreur ne s'est pas interceptée dans le bloc TRY et n'a jamais été envoyée au CATCH et il l'a donc fait un COMMIT alors qu'il n'aurait pas dû. Bien sûr, cela aurait pu être un problème de ne pas s'être XACT_ABORTmis à ONplutôt que de ne pas vérifier XACT_STATE(), ou peut-être les deux. Et je me souviens d'avoir lu quelque chose qui disait que si vous utilisez des serveurs liés et / ou des transactions distribuées, vous deviez utiliser XACT_ABORT ONet / ou XACT_STATE(), mais je n'arrive pas à trouver ce document maintenant. Si je le trouve, je le mettrai à jour avec le lien.

Pourtant, j'ai essayé plusieurs choses et je suis incapable de trouver un scénario qui a XACT_ABORT ONet passe le contrôle au CATCHbloc avec des XACT_STATE()rapports 1.

Essayez ces exemples pour voir l'effet de XACT_ABORTsur la valeur de XACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

MISE À JOUR

Bien que cela ne fasse pas partie de la question d'origine, sur la base de ces commentaires sur cette réponse:

J'ai lu les articles d'Erland sur le traitement des erreurs et des transactions où il dit que XACT_ABORTc'est OFFpar défaut pour des raisons héritées et que normalement nous devrions le régler ON.
...
"... si vous suivez la recommandation et exécutez avec SET XACT_ABORT ON, la transaction sera toujours vouée à l'échec."

Avant d'utiliser XACT_ABORT ONpartout, je me pose la question: qu'est-ce qui est exactement gagné ici? Je n'ai pas jugé nécessaire de le faire et je préconise généralement de ne l'utiliser que lorsque cela est nécessaire. Que vous le souhaitiez ou non ROLLBACKpeut être géré assez facilement en utilisant le modèle montré dans la réponse de @ Remus , ou celui que j'utilise depuis des années, c'est essentiellement la même chose mais sans le point de sauvegarde, comme indiqué dans cette réponse (qui gère les appels imbriqués):

Sommes-nous tenus de gérer la transaction en code C # ainsi qu'en procédure stockée


MISE À JOUR 2

J'ai fait un peu plus de tests, cette fois en créant une petite application console .NET, en créant une transaction dans la couche d'application, avant d'exécuter des SqlCommandobjets (c'est-à-dire via using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...), ainsi qu'en utilisant une erreur d'abandon de lot au lieu d'une simple instruction -aborting error, et a constaté que:

  1. Une transaction "non engageable" est une transaction qui, pour la plupart, a déjà été annulée (les modifications ont été annulées), mais @@TRANCOUNTest toujours> 0.
  2. Lorsque vous avez une transaction «non engageable», vous ne pouvez pas émettre un COMMITtel que cela générera et une erreur indiquant que la transaction est «non engageable». Vous ne pouvez pas non plus l'ignorer / ne rien faire car une erreur sera générée lorsque le lot aura fini de déclarer que le lot s'est terminé avec une transaction persistante et non validable et qu'il sera annulé (donc, euh, s'il se rétrogradera automatiquement de toute façon, pourquoi s'embêter à jeter l'erreur?). Vous devez donc émettre une explicite ROLLBACK, peut-être pas dans le CATCHbloc immédiat , mais avant la fin du lot.
  3. Dans une TRY...CATCHconstruction, quand XACT_ABORTest OFF, les erreurs qui mettraient fin automatiquement à la transaction si elles se sont produites en dehors d'un TRYbloc, telles que les erreurs d'abandon de lot, annuleront le travail mais ne mettront pas fin à la Tranasction, la laissant comme "non engageable". L'émission d'un ROLLBACKest plus une formalité nécessaire pour clôturer la transaction, mais le travail a déjà été annulé.
  4. Quand XACT_ABORTest ON, la plupart des erreurs agissent comme un abandon de lot, et se comportent donc comme décrit dans la puce directement ci-dessus (# 3).
  5. XACT_STATE(), au moins dans un CATCHbloc, affichera un -1pour les erreurs d'abandon de lot s'il y avait une transaction active au moment de l'erreur.
  6. XACT_STATE()retourne parfois 1même en l'absence de transaction active. Si @@SPID(parmi d'autres) est dans la SELECTliste avec XACT_STATE(), alors XACT_STATE()retournera 1 lorsqu'il n'y a pas de transaction active. Ce comportement a commencé dans SQL Server 2012 et existe en 2014, mais je n'ai pas testé en 2016.

En gardant à l'esprit les points ci-dessus:

  • Étant donné les points # 4 et # 5, puisque la plupart (ou toutes?) Les erreurs rendront une transaction "non engageable", il semble tout à fait inutile de vérifier XACT_STATE()le CATCHbloc quand XACT_ABORTc'est ONpuisque la valeur retournée sera toujours -1.
  • L'archivage XACT_STATE()du CATCHbloc quand XACT_ABORTest OFFplus logique car la valeur de retour aura au moins une certaine variation car elle renverra 1des erreurs d'abandon d'instruction. Cependant, si vous codez comme la plupart d'entre nous, cette distinction n'a aucun sens puisque vous appellerez de ROLLBACKtoute façon simplement le fait qu'une erreur s'est produite.
  • Si vous trouvez une situation qui ne justifie l' émission d' un COMMITdans le CATCHbloc, puis vérifier la valeur XACT_STATE(), et assurez - vous SET XACT_ABORT OFF;.
  • XACT_ABORT ONsemble offrir peu ou pas d'avantages sur la TRY...CATCHconstruction.
  • Je ne trouve aucun scénario où la vérification XACT_STATE()offre un avantage significatif par rapport à la simple vérification @@TRANCOUNT.
  • Je ne peux pas non plus trouver de scénario où les XACT_STATE()retours 1dans un CATCHbloc le XACT_ABORTsont ON. Je pense que c'est une erreur de documentation.
  • Oui, vous pouvez annuler une transaction que vous n'avez pas explicitement commencée. Et dans le contexte de l'utilisation XACT_ABORT ON, c'est un point discutable car une erreur se produisant dans un TRYbloc annulera automatiquement les modifications.
  • La TRY...CATCHconstruction a l'avantage XACT_ABORT ONde ne pas annuler automatiquement la transaction entière, et donc de permettre la transaction (tant que les XACT_STATE()retours 1) sont validés (même s'il s'agit d'un cas limite).

Exemple de XACT_STATE()retour -1quand XACT_ABORTest OFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

MISE À JOUR 3

Lié à l'élément n ° 6 de la section MISE À JOUR 2 (c.-à-d. Possible valeur incorrecte renvoyée par XACT_STATE()lorsqu'il n'y a pas de transaction active):

  • Le comportement étrange / erroné a commencé dans SQL Server 2012 (testé jusqu'à présent par rapport à 2012 SP2 et 2014 SP1)
  • Dans les versions SQL Server 2005, 2008 et 2008 R2, XACT_STATE()n'a pas signalé de valeurs attendues lorsqu'il était utilisé dans des déclencheurs ou des INSERT...EXECscénarios: xact_state () ne peut pas être utilisé de manière fiable pour déterminer si une transaction est condamnée . Cependant, dans ces 3 versions (je n'ai testé que sur 2008 R2), XACT_STATE()ne signale pas incorrectement 1lorsqu'il est utilisé dans un SELECTavec @@SPID.
  • Un bogue Connect a été déposé contre le comportement mentionné ici, mais il est fermé en tant que «By Design»: XACT_STATE () peut renvoyer un état de transaction incorrect dans SQL 2012 . Cependant, le test a été effectué lors de la sélection à partir d'un DMV et il a été conclu que cela aurait naturellement une transaction générée par le système, au moins pour certains DMV. Il a également été déclaré dans la réponse finale des États membres que:

    Notez qu'une instruction IF et également un SELECT sans FROM ne démarrent pas de transaction.
    par exemple, l'exécution de SELECT XACT_STATE () si vous n'avez pas de transaction déjà existante retournera 0.

    Ces déclarations sont incorrectes dans l'exemple suivant:

    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
    GO
    DECLARE @SPID INT;
    SET @SPID = @@SPID;
    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
    GO
  • Par conséquent, le nouveau bogue Connect:
    XACT_STATE () renvoie 1 lorsqu'il est utilisé dans SELECT avec certaines variables système mais sans clause FROM

VEUILLEZ NOTER que dans l'élément "XACT_STATE () peut renvoyer un état de transaction incorrect dans SQL 2012" Élément de connexion lié directement ci-dessus, Microsoft (enfin, un représentant de) déclare:

@@ trancount renvoie le nombre d'instructions BEGIN TRAN. Il ne s'agit donc pas d'un indicateur fiable de l'existence d'une transaction active. XACT_STATE () renvoie également 1 s'il existe une transaction de validation automatique active, et est donc un indicateur plus fiable de l'existence d'une transaction active.

Cependant, je ne trouve aucune raison de ne pas faire confiance @@TRANCOUNT. Le test suivant montre que @@TRANCOUNTcela revient 1en effet dans une transaction de validation automatique:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

J'ai également testé sur une vraie table avec un déclencheur et @@TRANCOUNTdans le déclencheur, j'ai fait un rapport précis 1même si aucune transaction explicite n'avait été lancée.

Solomon Rutzky
la source
4

La programmation défensive vous oblige à écrire du code qui gère autant d'états connus que possible, réduisant ainsi la possibilité de bogues.

La vérification de XACT_STATE () pour déterminer si une restauration peut être exécutée est simplement une bonne pratique. Une tentative aveugle de restauration signifie que vous pouvez par inadvertance provoquer une erreur dans votre TRY ... CATCH.

Une façon dont une restauration peut échouer dans un TRY ... CATCH serait si vous n'avez pas explicitement démarré une transaction. Copier et coller des blocs de code peut facilement en être la cause.

Max Vernon
la source
Merci de votre réponse. Je ne pouvais tout simplement pas penser à un cas où le simple ROLLBACKne fonctionnerait pas à l'intérieur du CATCHet vous avez donné un bon exemple. Je suppose que cela peut également devenir rapidement compliqué si des transactions imbriquées et des procédures stockées imbriquées avec les leurs TRY ... CATCH ... ROLLBACKsont impliquées.
Vladimir Baranov
Still, I would appreciate it, if you could extend your answer regarding the second part - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; How can we end up inside the CATCH block with committable transaction? I wouldn't dare to commit some (possible) garbage from inside the CATCH. My reasoning is: if we are inside the CATCH something did go wrong, I can't trust the state of the database, so I'd better ROLLBACK whatever we've got.
Vladimir Baranov