5

La question sous-jacente à cet article est "Pourquoi une transaction LTM non promue est-elle jamais mise en doute?"TransactionInDoubtException à l'aide de System.Transactions sur SQL Server 2005

Je reçois System.Transactions.TransactionInDoubtException et je ne peux pas expliquer pourquoi. Malheureusement, je ne peux pas reproduire ce problème, mais selon les fichiers de trace, cela arrive. J'utilise SQL 2005, en me connectant à une base de données et en utilisant une SQLConnection, donc je ne m'attends pas à une promotion. Le message d'erreur indique un délai d'expiration. Cependant, parfois, je reçois un message de délai d'attente, mais l'exception est que la transaction a avorté par opposition à un doute, ce qui est beaucoup plus facile à gérer.

Voici la trace complète de la pile:

System.Transactions.TransactionInDoubtException: The transaction is in doubt. ---> System.Data.SqlClient.SqlException: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. 
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) 
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) 
    at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket() 
    at System.Data.SqlClient.TdsParserStateObject.ReadBuffer() 
    at System.Data.SqlClient.TdsParserStateObject.ReadByte() 
    at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment) 
    --- End of inner exception stack trace --- 
    at System.Transactions.TransactionStateInDoubt.EndCommit(InternalTransaction tx) 
    at System.Transactions.CommittableTransaction.Commit() 
    at System.Transactions.TransactionScope.InternalDispose() 
    at System.Transactions.TransactionScope.Dispose() 

Toutes les idées? Pourquoi suis-je dans le doute et que dois-je faire quand je l'obtiens?

EDIT pour plus d'informations

En fait, je ne comprends toujours pas la réponse pour cela. Ce que je me suis rendu compte est que la transaction s'engage en partie partiellement. Une table reçoit l'insertion mais l'autre n'obtient pas la mise à jour. Le code est LOURDEMENT tracé et il n'y a pas beaucoup de place pour que je manque quelque chose.

Y at-il un moyen que je peux facilement savoir si la transaction a été promue. Pouvons-nous dire à partir de la trace de la pile si c'est le cas? La phase SIngle commit (qui est dans la trace de Strack) semble ne pas m'avoir donné de promotion, mais peut-être qu'il me manque quelque chose. Si ce n'est pas promu alors comment peut-il être dans le doute.

Une autre pièce intéressante au puzzle est que je crée un clone de la transaction en cours. Je fais cela comme un travail sur cette question. http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=914869&SiteID=1

Malheureusement, je ne sais pas si ce problème a été résolu. Peut-être que créer le clone cause un problème. Voici le code correspondant

using (TransactionScope ts = new TransactionScope()) 
{ 
    transactionCreated = true; 
    //part of the workarround for microsoft defect mentioned in the beginning of this class 
    Transaction txClone = Transaction.Current.Clone(); 
    transactions[txClone] = txClone; 
    Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(TransactionCompleted); 
    MyTrace.WriteLine("Transaction clone stored and attached to event"); 

    m_dataProvider.PersistPackage(ControllerID, package); 
    MyTrace.WriteLine("Package persisted"); 
    m_dataProvider.PersistTransmissionControllerStatus(this); 
    MyTrace.WriteLine("Transmission controlled updated"); 
    ts.Complete(); 
} 

Merci

+0

la base de données en miroir? –

+0

Non, la base de données n'est pas mise en miroir. Cela ne se produit pas seulement dans un ou deux environnements mais dans des douzaines d'entre eux – Mark

+0

Avez-vous essayé de demander sur [serverfault] (http://serverfault.com/)? Ou même en mettant une demande surport avec Microsoft. S'il vous plaît poster la réponse quand vous le trouvez ... –

Répondre

2

La réponse est qu'il ne peut pas. Ce qui se passait apparemment, c'était que la promotion avait lieu. (Nous avons accidentellement découvert cela) Je ne sais toujours pas comment détecter si une tentative de promotion est en cours. Cela aurait été extreamly utile pour détecter cela.

+3

Vous pourriez avoir manipulé le [DistributedTransactionStarted] (http: //msdn.microsoft.com/en-us/library/system.transactions.transactionmanager.distributedtransactionstarted.aspx) event. –

0

Difficile de conseiller quoi que ce soit sans regarder dans votre code, mais ma première suggestion est que TransactionScope() est une tête lorsque vous avez 1 serveur SQL avec 1 connexion. Pourquoi ne pas utiliser plutôt System.Data.SqlClient.SqlTransaction() à la place? Documentation: "Si une connexion à un serveur distant est ouverte dans une transaction de base de données, la connexion au serveur distant est enrôlée dans la transaction distribuée et la transaction locale est automatiquement promue dans une transaction distribuée." Toutefois, si vous utilisez vraiment une seule connexion est une erreur très étrange. Êtes-vous sûr que vous n'appelez aucun composant tiers qui peut créer des connexions à MS SQL, MS MQ ou autre chose qui nécessitera une transaction distibuted à créer?

De plus, si vous utilisez TransactionScope() dans la procédure CLR de SQL Server, il favorisera la transaction dans tous les cas.

De même si vous appelez une procédure de magasin qui accède à une table à partir d'un serveur SQL lié, je suppose que cela nécessitera également une promotion.

La question est assez ancienne, peut-être connaissez-vous déjà la réponse et pourriez la poster ici pour les autres. Merci!

+0

1) J'ai donné un petit extrait de code 2) Je pourrais utiliser SqlTransaction mais à partir d'un système de point de vue OO.Transactions est une abstraction belle et soignée 3) Tout indique qu'une seule connexion est utilisée. Je ne suis pas sûr comment je pourrais prouver de façon concluante ce 4) Aucun componenets de tiers dans cette zone du code 5) Ce n'est pas dans un CLR 6) Aucun serveur lié – Mark

0

Beats the diable hors de moi.

J'ai l'habitude de faire ExecuteNonQuery sur "BEGIN TRANSACTION" et "COMMIT" ou "ROLLBACK" à la main. Tout à fait par hasard cela a très bien fonctionné quand un code devait fonctionner de la même façon, que ce soit dans une transaction ou non.

0

Je suis en train d'avoir le même problème et il semble être lié aux spécifications du serveur db. Je voudrais que votre dba jette un coup d'oeil à l'utilisation du processeur de la boîte pendant que vous exécutez ce code. Cela se produit dans notre environnement car nous tentons une opération de mise à jour sur un grand nombre de lignes dans notre base de données au sein d'une transaction. Cela se passe sur notre base de données OLTP sur l'une de nos tables les plus utilisées qui va créer des conflits de verrous. Ce que je trouve fascinant à propos du problème, c'est l'aspect de délai d'expiration que je vois dans votre trace de pile. Quelles que soient les valeurs d'expiration que vous définissez, que ce soit sur la commande ou en tant qu'argument du constructeur de TransactionScope, cela ne semble pas résoudre le problème. La façon dont je vais aborder la question est de réduire les commits. Espérons que cela aide

11

La réponse actuellement acceptée est qu'une transaction LTM (non-MSDTC) non promue ne peut jamais être mise en doute. Après beaucoup de recherches sur un problème similaire, j'ai trouvé que c'est incorrect. En raison de la façon dont le protocole de validation monophasé est implémenté, il y a une petite période pendant laquelle la transaction est "en doute", après que le Gestionnaire de transactions a envoyé la requête SinglePhaseCommit à son subordonné, et avant les réponses subordonnées avec soit un message validé/abandonné/ou préparé (doit être promu/transmis au MSDTC). Si la connexion est perdue pendant ce temps, la transaction est "en doute", b/c le TransactionManager n'a jamais reçu de réponse lorsqu'il a demandé au subordonné d'exécuter un SinglePhaseCommit.

De MSDN Single-Phase Commit, voir aussi « validation à phase unique flux » image au bas de cette réponse:

Il y a un inconvénient possible de cette optimisation: si le gestionnaire de transactions perd le contact avec le participant subordonné après avoir envoyé la demande de validation monophasée mais avant de recevoir une notification de résultat , il n'a aucun mécanisme fiable pour récupérer le résultat réel de la transaction. Par conséquent, la transaction gestionnaire envoie un dans les résultats de doute à toutes les demandes ou les électeurs en attente de notification des résultats d'information

On y trouve aussi quelques exemples pratiques de choses que j'ai trouvé que la promotion cause System.Transaction/escalade à un MSDTC transaction (ce n'est pas directement lié à l'OP, mais je l'ai trouvé très utile Testé dans VS 2013, SQL Server 2008 R2, .NET 4.5, sauf indication contraire.):

  1. (celui-ci est spécifique à SQL Server 2005 ou si le niveau de compatibilité < 100) - Appel Connection.Open() plus d'une fois à tout moment avec n un TransactionScope. Cela inclut également l'appel de .Open(), .Close(), .Open() sur l'instance de connexion SAME.
  2. Ouverture imbriquées connexions dans un TransactionScope
  3. L'utilisation de plusieurs connexions ne pas utiliser la connexion mise en commun, même si elles ne sont pas imbriqués et la connexion à la même base de données.
  4. Requêtes impliquant des serveurs liés
  5. Procédures SQL CLR utilisant TransactionScope. Voir: http://technet.microsoft.com/en-us/library/ms131084.aspx "TransactionScope doit être utilisé uniquement lorsque des sources de données locales et distantes ou des gestionnaires de ressources externes sont utilisés, car TransactionScope [dans CLR] fait toujours la promotion des transactions, même si elle est utilisée uniquement dans une connexion de contexte "
  6. Il semble que si vous utilisez le regroupement de connexions et que la même connexion physique que celle utilisée dans Connection1 n'est pas disponible pour une raison quelconque dans Connections" 2 à N ", alors la transaction entière sera promue (b/c en tant que 2 ressources durables distinctes, l'item # 2 est la liste officielle des EM ci-dessous). Je n'ai pas testé/confirmé ce cas particulier, mais je comprends comment cela fonctionne. Cela a du sens b/c dans les coulisses c'est similaire à l'utilisation de connexions imbriquées ou ne pas utiliser le regroupement de connexions b/c plusieurs connexions physiques sont utilisées. http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx "Lorsqu'une connexion est fermée et renvoyée au pool avec une transaction System.Transactions inscrite, elle est mise de côté de telle sorte que la prochaine requête pour ce pool de connexion avec la même transaction System.Transactions renvoie la même connexion si elle Si une telle demande est émise et qu'aucune connexion groupée n'est disponible, une connexion est établie à partir de la partie non-transactionnée du pool et enregistrée "

Et voici la liste officielle des États-Unis des causes escalation: http://msdn.microsoft.com/en-us/library/ms229978(v=vs.85).aspx

  1. Au moins une ressource durable ne prenant pas en charge les notifications monophasées est enlis dans la transaction.
  2. Au moins deux ressources durables prenant en charge les notifications monophasées sont inscrites dans la transaction. Par exemple, l'inscription d'une seule connexion à SQL Server 2005 n'entraîne pas la promotion d'une transaction. Toutefois, chaque fois que vous ouvrez une deuxième connexion à une base de données SQL Server 2005 entraînant l'inscription de la base de données, l'infrastructure System.Transactions détecte qu'il s'agit de la deuxième ressource durable dans la transaction et l'escalade vers une transaction MSDTC.
  3. Une demande de "marshalage" de la transaction vers un domaine d'application différent ou un processus différent est appelée. Par exemple, la sérialisation de l'objet de transaction sur une frontière de domaine d'application. L'objet de transaction est marshaled-by-value, ce qui signifie que toute tentative de passage à travers une frontière de domaine d'application (même dans le même processus) entraîne la sérialisation de l'objet de transaction. Vous pouvez passer les objets de transaction en effectuant un appel sur une méthode distante qui prend une transaction en tant que paramètre ou vous pouvez essayer d'accéder à un composant de service transactionnel distant. Cela sérialise l'objet de transaction et entraîne une escalade, comme lorsqu'une transaction est sérialisée dans un domaine d'application. Il est distribué et le gestionnaire des transactions locales n'est plus suffisant.

Single phase commit flow