J'ai lu MSDN sur TRY...CATCH
et XACT_STATE
.
Il a l'exemple suivant qui utilise XACT_STATE
dans le CATCH
bloc d'une TRY…CATCH
construction 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_STATE
retours?
Veuillez noter que l'indicateur XACT_ABORT
est défini ON
dans 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é CATCH
et 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 CATCH
avec une transaction qui peut être validée quand XACT_ABORT
est définie surON
.
L'article MSDN sur SET XACT_ABORT
a un exemple lorsque certaines instructions à l'intérieur d'une transaction s'exécutent avec succès et certaines échouent lorsque XACT_ABORT
est défini sur OFF
, je comprends cela. Mais, SET XACT_ABORT ON
comment peut-il arriver queXACT_STATE()
renvoie 1 à l'intérieur du CATCH
bloc?
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 ON
le CATCH
bloc, 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
la source
XACT_ABORT
àON
ouOFF
.TL; DR / Résumé: Concernant cette partie de la question:
J'ai fait pas mal de tests à ce sujet maintenant et je ne trouve aucun cas où
XACT_STATE()
retourne à l'1
intérieur d'unCATCH
bloc quand@@TRANCOUNT > 0
et la propriété de session deXACT_ABORT
estON
. Et en fait, selon la page MSDN actuelle pour SET XACT_ABORT :Cette déclaration semble être en accord avec vos spéculations et mes conclusions.
Vrai, mais les instructions de cet exemple ne se trouvent pas dans un
TRY
bloc. Ces mêmes déclarations dans unTRY
bloc empêcheraient encore l' exécution de toute déclaration après celle qui a causé l'erreur, mais en supposant queXACT_ABORT
estOFF
, lorsque le contrôle est passé auCATCH
bloc 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é, siXACT_ABORT
c'est le cas,ON
toutes les modifications antérieures sont automatiquement annulées, puis vous avez le choix entre: a) émettre unROLLBACK
qui 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@@TRANCOUNT
pour0
, 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_ABORT
est 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 à laTRY...CATCH
construction qui était introduit dans SQL Server 2005. En consultant à nouveau cette documentation et en regardant l'exemple ( sans leTRY...CATCH
), l'utilisationXACT_ABORT ON
entraî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 cetteSET XACT_ABORT
documentation).Je pense qu'il est raisonnable de conclure que:
TRY...CATCH
construction dans SQL Server 2005 a créé le besoin d'un nouvel état de transaction (c'est-à-dire "non validable") et de laXACT_STATE()
fonction pour obtenir ces informations.XACT_STATE()
unCATCH
bloc n'a de sens que si les deux conditions suivantes sont vraies:XACT_ABORT
estOFF
(sinonXACT_STATE()
devrait toujours revenir-1
et@@TRANCOUNT
serait tout ce dont vous avez besoin)CATCH
bloc, ou quelque part dans la chaîne si les appels sont imbriqués, ce qui apporte une modification (uneCOMMIT
ou même n'importe quelle instruction DML, DDL, etc.) au lieu de faire uneROLLBACK
. (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 à laXACT_STATE()
place de@@TRANCOUNT
, et pourquoi les tests montrent que leur raisonnement ne fonctionne pas.TRY...CATCH
construction dans SQL Server 2005 a, pour la plupart, obsolète laXACT_ABORT ON
propriété de session car elle offre un plus grand degré de contrôle sur la transaction (vous avez au moins la possibilité de le faireCOMMIT
, à condition queXACT_STATE()
cela ne revienne pas-1
). Avant SQL Server 2005 , uneautre 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
XACT_STATE()
est erronée, ou au mieux induire en erreur, en ce sens qu'elle montre la vérification deXACT_STATE() = 1
quandXACT_ABORT
estON
.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
CATCH
bloc quandXACT_ABORT
estON
), 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.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 un1
, cela signifie que la transaction est "validable", que vous avez le choix entreCOMMIT
ouROLLBACK
. 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 faireROLLBACK
car 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 ON
change un peu les choses. Vous pouvez avoir une erreur régulière, aprèsBEGIN TRAN
cela, passera le contrôle au bloc CATCH quandXACT_ABORT
isOFF
et XACT_STATE () reviendront1
. MAIS, si XACT_ABORT estON
, alors la transaction est "abandonnée" (c'est-à-dire invalidée) pour toute erreur 'ol puisXACT_STATE()
reviendra-1
. Dans ce cas, il semble inutile de vérifierXACT_STATE()
dans leCATCH
bloc car il semble toujours renvoyer un-1
quandXACT_ABORT
estON
.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:La page MSDN pour SET XACT_ABORT , sous la section "Remarques", dit:
et:
La page MSDN pour BEGIN TRANSACTION , dans la section "Remarques", indique:
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_ABORT
mis àON
plutôt que de ne pas vérifierXACT_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 utiliserXACT_ABORT ON
et / ouXACT_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 ON
et passe le contrôle auCATCH
bloc avec desXACT_STATE()
rapports1
.Essayez ces exemples pour voir l'effet de
XACT_ABORT
sur la valeur deXACT_STATE()
:MISE À JOUR
Bien que cela ne fasse pas partie de la question d'origine, sur la base de ces commentaires sur cette réponse:
Avant d'utiliser
XACT_ABORT ON
partout, 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 nonROLLBACK
peut ê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
SqlCommand
objets (c'est-à-dire viausing (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:@@TRANCOUNT
est toujours> 0.COMMIT
tel 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 expliciteROLLBACK
, peut-être pas dans leCATCH
bloc immédiat , mais avant la fin du lot.TRY...CATCH
construction, quandXACT_ABORT
estOFF
, les erreurs qui mettraient fin automatiquement à la transaction si elles se sont produites en dehors d'unTRY
bloc, 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'unROLLBACK
est plus une formalité nécessaire pour clôturer la transaction, mais le travail a déjà été annulé.XACT_ABORT
estON
, 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 unCATCH
bloc, affichera un-1
pour les erreurs d'abandon de lot s'il y avait une transaction active au moment de l'erreur.XACT_STATE()
retourne parfois1
même en l'absence de transaction active. Si@@SPID
(parmi d'autres) est dans laSELECT
liste avecXACT_STATE()
, alorsXACT_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:
XACT_STATE()
leCATCH
bloc quandXACT_ABORT
c'estON
puisque la valeur retournée sera toujours-1
.XACT_STATE()
duCATCH
bloc quandXACT_ABORT
estOFF
plus logique car la valeur de retour aura au moins une certaine variation car elle renverra1
des erreurs d'abandon d'instruction. Cependant, si vous codez comme la plupart d'entre nous, cette distinction n'a aucun sens puisque vous appellerez deROLLBACK
toute façon simplement le fait qu'une erreur s'est produite.COMMIT
dans leCATCH
bloc, puis vérifier la valeurXACT_STATE()
, et assurez - vousSET XACT_ABORT OFF;
.XACT_ABORT ON
semble offrir peu ou pas d'avantages sur laTRY...CATCH
construction.XACT_STATE()
offre un avantage significatif par rapport à la simple vérification@@TRANCOUNT
.XACT_STATE()
retours1
dans unCATCH
bloc leXACT_ABORT
sontON
. Je pense que c'est une erreur de documentation.XACT_ABORT ON
, c'est un point discutable car une erreur se produisant dans unTRY
bloc annulera automatiquement les modifications.TRY...CATCH
construction a l'avantageXACT_ABORT ON
de ne pas annuler automatiquement la transaction entière, et donc de permettre la transaction (tant que lesXACT_STATE()
retours1
) sont validés (même s'il s'agit d'un cas limite).Exemple de
XACT_STATE()
retour-1
quandXACT_ABORT
estOFF
: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):XACT_STATE()
n'a pas signalé de valeurs attendues lorsqu'il était utilisé dans des déclencheurs ou desINSERT...EXEC
scé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 incorrectement1
lorsqu'il est utilisé dans unSELECT
avec@@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:
Ces déclarations sont incorrectes dans l'exemple suivant:
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:
Cependant, je ne trouve aucune raison de ne pas faire confiance
@@TRANCOUNT
. Le test suivant montre que@@TRANCOUNT
cela revient1
en effet dans une transaction de validation automatique:J'ai également testé sur une vraie table avec un déclencheur et
@@TRANCOUNT
dans le déclencheur, j'ai fait un rapport précis1
même si aucune transaction explicite n'avait été lancée.la source
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.
la source
ROLLBACK
ne fonctionnerait pas à l'intérieur duCATCH
et 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 leursTRY ... CATCH ... ROLLBACK
sont impliquées.IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
How can we end up inside theCATCH
block with committable transaction? I wouldn't dare to commit some (possible) garbage from inside theCATCH
. My reasoning is: if we are inside theCATCH
something did go wrong, I can't trust the state of the database, so I'd betterROLLBACK
whatever we've got.