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:
- 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.
- archiver
XACT_STATE()un CATCHbloc n'a de sens que si les deux conditions suivantes sont vraies:
XACT_ABORTest OFF(sinon XACT_STATE()devrait toujours revenir -1et @@TRANCOUNTserait tout ce dont vous avez besoin)
- 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.
- 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
- 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:
- 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.
- 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.
- 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é.
- 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).
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.
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.
XACT_ABORTàONouOFF.