TransactionScope passant automatiquement à MSDTC sur certaines machines?

284

Dans notre projet, nous utilisons TransactionScope pour garantir que notre couche d'accès aux données exécute ses actions dans une transaction. Nous visons à ne pas exiger que le service MSDTC soit activé sur les machines de nos utilisateurs finaux.

Le problème est que, sur la moitié de nos machines de développement, nous pouvons exécuter MSDTC désactivé. L'autre moitié doit l'avoir activé ou ils obtiennent le message d'erreur «MSDTC sur [SERVEUR] n'est pas disponible» .

Cela m'a vraiment fait me gratter la tête et m'a sérieusement envisagé de revenir à une solution de type TransactionScope personnalisée basée sur des objets de transaction ADO.NET. Il est apparemment fou - le même code que les travaux (et ne dégénèrent pas) sur la moitié de notre développeur est fait AMPLIFIENT l'autre développeur de.

J'espérais une meilleure réponse à Trace pourquoi une transaction est escaladée en DTC mais malheureusement ce n'est pas le cas.

Voici un exemple de code qui causera le problème, sur les machines qui tentent de s'intensifier, il essaie de s'intensifier sur la deuxième connexion.Open () (et oui, aucune autre connexion n'est ouverte à ce moment-là.)

using (TransactionScope transactionScope = new TransactionScope() {
   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();
         using (SqlDataReader reader = command.ExecuteReader()) {
            // use the reader
            connection.Close();
         }
      }
   }

   // Do other stuff here that may or may not involve enlisting 
   // in the ambient transaction

   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();  // Throws "MSDTC on [SERVER] is unavailable" on some...

         // gets here on only half of the developer machines.
      }
      connection.Close();
   }

   transactionScope.Complete();
}

Nous avons vraiment creusé et essayé de comprendre cela. Voici quelques informations sur les machines sur lesquelles il fonctionne:

  • Dev 1: Windows 7 x64 SQL2008
  • Dev 2: Windows 7 x86 SQL2008
  • Dev 3: Windows 7 x64 SQL2005 SQL2008

Développeurs sur lesquels il ne fonctionne pas:

  • Dev 4: Windows 7 x64, SQL2008 SQL2005
  • Dev 5: Windows Vista x86, SQL2005
  • Dev 6: Windows XP X86, SQL2005
  • Mon PC personnel: Windows Vista Home Premium, x86, SQL2005

Je dois ajouter que toutes les machines, afin de traquer le problème, ont été entièrement corrigées avec tout ce qui est disponible à partir de Microsoft Update.

Mise à jour 1:

Cette page d'escalade de transactions MSDN indique que les conditions suivantes entraîneront l'escalade d'une transaction en DTC:

  1. Au moins une ressource durable qui ne prend pas en charge les notifications monophasées est inscrite dans la transaction.
  2. Au moins deux ressources durables qui prennent en charge les notifications en une seule phase sont inscrites dans la transaction. Par exemple, l'inscription d'une connexion unique avec n'entraîne pas la promotion d'une transaction. Cependant, chaque fois que vous ouvrez une deuxième connexion à une base de données provoquant l'enrôlement 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 en une transaction MSDTC.
  3. Une demande de "marshaler" la transaction vers un domaine d'application différent ou un processus différent est invoquée. Par exemple, la sérialisation de l'objet de transaction à travers une limite de domaine d'application. L'objet de transaction est marshalé par valeur, ce qui signifie que toute tentative de le traverser 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 transmettre les objets de transaction en effectuant un appel sur une méthode distante qui prend une transaction comme paramètre ou vous pouvez essayer d'accéder à un composant desservi par transaction à distance. Cela sérialise l'objet de transaction et entraîne une escalade, comme lorsqu'une transaction est sérialisée sur un domaine d'application. Il est en cours de distribution et le gestionnaire de transactions local n'est plus adéquat.

Nous ne connaissons pas le n ° 3. # 2 ne se produit pas car il n'y a qu'une seule connexion à la fois, et c'est aussi vers une seule «ressource durable». Y a-t-il un moyen que le n ° 1 puisse se produire? Une configuration SQL2005 / 8 qui l'empêche de prendre en charge les notifications monophasées?

Mise à jour 2:

A réexaminé, personnellement, les versions de SQL Server de tout le monde - "Dev 3" a en fait SQL2008 et "Dev 4" est en fait SQL2005. Cela m'apprendra à ne plus jamais faire confiance à mes collègues. ;) En raison de ce changement de données, je suis presque sûr que nous avons trouvé notre problème. Nos développeurs SQL2008 ne rencontraient pas le problème parce que SQL2008 a de nombreuses quantités impressionnantes incluses que SQL2005 n'a pas.

Cela me dit également que parce que nous allons prendre en charge SQL2005, nous ne pouvons pas utiliser TransactionScope comme nous l'avons été, et si nous voulons utiliser TransactionScope, nous allons devoir passer un seul objet SqlConnection autour ... ce qui semble problématique dans les situations où la SqlConnection ne peut pas être facilement contournée ... elle sent juste l'instance de global-SqlConnection. Banc!

Mise à jour 3

Juste pour clarifier ici dans la question:

SQL2008:

  • Autorise plusieurs connexions au sein d'un même TransactionScope (comme illustré dans l'exemple de code ci-dessus.)
  • Avertissement 1: si ces multiples SqlConnections sont imbriquées, c'est-à-dire que deux ou plusieurs SqlConnections sont ouvertes en même temps, TransactionScope passera immédiatement au DTC.
  • Mise en garde n ° 2: si une SqlConnection supplémentaire est ouverte vers une autre «ressource durable» (c'est-à-dire: un autre serveur SQL), elle passera immédiatement à DTC

SQL2005:

  • N'autorise pas plusieurs connexions dans un même TransactionScope, point. Il s'aggravera quand / si une seconde SqlConnection est ouverte.

Mise à jour 4

Dans l'intérêt de rendre cette question encore plus compliquée , et pour plus de clarté, voici comment vous pouvez faire passer SQL2005 en DTC avec un seul SqlConnection :

using (TransactionScope transactionScope = new TransactionScope()) {
   using (SqlConnection connection = new SqlConnection(connectionString)) {
      connection.Open();
      connection.Close();
      connection.Open(); // escalates to DTC
   }
}

Cela me semble tout simplement cassé, mais je suppose que je peux comprendre si chaque appel à SqlConnection.Open()est en train de s'emparer du pool de connexions.

"Mais pourquoi cela pourrait-il arriver?" Eh bien, si vous utilisez un SqlTableAdapter sur cette connexion avant son ouverture, le SqlTableAdapter ouvrira et fermera la connexion, terminant ainsi la transaction pour vous car vous ne pouvez plus la rouvrir.

Donc, pour utiliser TransactionScope avec SQL2005, vous devez disposer d'une sorte d'objet de connexion globale qui reste ouvert à partir du moment où le premier TransactionScope est instancié jusqu'à ce qu'il ne soit plus nécessaire. Outre l'odeur de code d'un objet de connexion globale, ouvrir la connexion en premier et la fermer en dernier est contraire à la logique d'ouvrir une connexion le plus tard possible et de la fermer le plus tôt possible.

Yoopergeek
la source
Pouvez-vous développer le "Faites d'autres choses ici qui peuvent ou non impliquer dans la transaction ambiante". Ce qui est là-dedans affecte-t-il grandement le comportement du code?
RichardOD
2
"# 2 ne se produit pas car il n'y a qu'une seule connexion à la fois" - # 2 ne dit pas que la deuxième connexion doit être ouverte en même temps, juste qu'elle doit être enrôlée dans la même transaction.
Joe
3
Merci beaucoup d'avoir signalé la mise à jour 4 montrant comment l'escalade peut se produire avec un seul SqlConnection. C'est exactement ce que je rencontrais malgré le fait de m'assurer soigneusement qu'une seule SqlConnection est utilisée. C'est agréable de savoir que c'est l'ordinateur qui est fou et pas moi. :-)
Oran Dennison
En termes de regroupement de connexions, si nous avons plusieurs connexions (et imbriquées si nécessaire), si nous ouvrons et fermons une à la fois, utilisons-nous 1 ressource de pool de connexion réelle ou 1 par connexion, j'essaie de rationaliser cela afin de déterminer si oui ou non avoir une connexion "enrôlable" de portée appropriée (que j'aimerais éviter)
brumScouse
1
Les connexions imbriquées sous la même étendue de transaction se transformeront en une transaction distribuée. À partir de SQL Server 2008 et supérieur, plusieurs connexions (non imbriquées) sous la même étendue de transaction ne seront pas promues en une transaction distribuée.
PreguntonCojoneroCabrón

Réponses:

71

SQL Server 2008 peut utiliser plusieurs SQLConnections en un TransactionScopesans escalade, à condition que les connexions ne soient pas ouvertes en même temps, ce qui entraînerait plusieurs connexions TCP «physiques» et nécessiterait donc une escalade.

Je vois que certains de vos développeurs ont SQL Server 2005 et d'autres ont SQL Server 2008. Etes-vous sûr d'avoir correctement identifié ceux qui sont en escalade et ceux qui ne le sont pas?

L'explication la plus évidente serait que les développeurs avec SQL Server 2008 sont ceux qui n'escaladent pas.

Joe
la source
Oui, les détails sont corrects, et quelqu'un regarde-t-il réellement le code? Il y a deux connexions dans la portée de la transaction, cependant, il n'y a qu'une seule connexion instanciée et ouverte à un seul instant. En outre, non, DTC ne fonctionne pas sur les machines qui fonctionnent.
Yoopergeek
1
"cependant, il n'y a jamais qu'une seule connexion instanciée et ouverte à un seul instant" - pourquoi est-ce pertinent? Avec SQL2005, si vous ouvrez plusieurs connexions dans une étendue de transaction, vous augmenterez si elles restent ouvertes simultanément ou non. Ce qui est logique si vous y réfléchissez.
Joe
Vous et les hwiechers me faites maintenant des devinettes et je suis impatient de travailler lundi et d'inspecter leurs machines individuelles de plus près et de m'assurer que les versions de SQL Server sont comme indiqué précédemment.
Yoopergeek
19
Vous et les hwiechers avez raison. J'ai un œuf sur tout le visage. Merci de m'avoir frappé avec la clé. :) Parce que vous étiez le premier, vous obtenez la réponse. Je voudrais ajouter un point de clarification, cependant - SQL2008 permet d'ouvrir plusieurs connexions, mais pas en même temps. Il ne peut toujours y avoir qu'une seule connexion ouverte à un moment donné ou TransactionScope passera au DTC.
Yoopergeek
@Yoopergeek J'ai pu vérifier que votre "pas en même temps" est important et j'ai modifié la réponse de @Joe en conséquence. La surveillance des connexions TCP pendant les tests a montré que l'ancienne connexion TCP sera réutilisée lorsque les connexions ne sont pas utilisées en même temps, et donc TransactionScopepeut se contenter d'un seul COMMITcôté serveur, ce qui rendrait l'escalade superflue.
Eugene Beresovsky,
58

Le résultat de mes recherches sur le sujet:

entrez la description de l'image ici

Voir Éviter l'escalade indésirable vers des transactions distribuées

J'étudie toujours le comportement d'escalade d'Oracle: les transactions couvrant plusieurs connexions à la même base de données sont-elles transmises au DTC?

Peter Meinl
la source
1
Merci d'avoir partagé vos recherches. Ça m'a vraiment aidé. Encore une question rapide. Quelle est la différence entre TransactionScope () et sqlConnection.BeginTransaction ()?
Baig
Selon cette demande de fonctionnalité , ODAC 12C devrait maintenant se comporter comme SQL 2008, ne pas passer à distribué lors de l'utilisation de connexions consécutives à la même source de données.
Frédéric
31

Ce code va provoquer une escalade lors de la connexion à 2005.

Consultez la documentation sur MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx

Transactions promouvables dans SQL Server 2008

Dans la version 2.0 du .NET Framework et SQL Server 2005, l'ouverture d'une deuxième connexion à l'intérieur d'un TransactionScope promouvrait automatiquement la transaction en une transaction distribuée complète, même si les deux connexions utilisaient des chaînes de connexion identiques. Dans ce cas, une transaction distribuée ajoute une surcharge inutile qui diminue les performances.

À partir de SQL Server 2008 et de la version 3.5 du .NET Framework, les transactions locales ne sont plus promues en transactions distribuées si une autre connexion est ouverte dans la transaction après la fermeture de la transaction précédente. Cela ne nécessite aucune modification de votre code si vous utilisez déjà le regroupement de connexions et l'inscription dans les transactions.

Je ne peux pas expliquer pourquoi Dev 3: Windows 7 x64, SQL2005 réussit et Dev 4: Windows 7 x64 échoue. Êtes-vous sûr que ce n'est pas l'inverse?

hwiechers
la source
10

Je ne sais pas pourquoi cette réponse a été supprimée, mais cela semble contenir des informations pertinentes.

répondu le 4 août 10 à 17:42 Eduardo

  1. Définissez Enlist = false sur la chaîne de connexion pour éviter l'inscription automatique sur la transaction.

  2. Inscrire manuellement la connexion en tant que participants dans la portée de la transaction [ article d'origine obsolète] ou procédez comme suit: Comment empêcher la promotion MSDTC automatique [archive.is]

Chris Marisic
la source
msdn.microsoft.com/en-us/library/ms172153%28v=VS.80%29.aspx introuvable, documentation retirée de Visual Studio 2005
Kiquenet
2

Je ne sais pas trop si la connexion imbriquée est le problème. J'appelle une instance locale de SQL Server et cela ne génère pas le DTC ??

    public void DoWork2()
    {
        using (TransactionScope ts2 = new TransactionScope())
        {
            using (SqlConnection conn1 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;"))
            {
                SqlCommand cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                cmd.Connection = conn1;
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();

                using (SqlConnection conn2 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;Connection Timeout=100"))
                {
                    cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                    cmd.Connection = conn2;
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
            }

            ts2.Complete();
        }
    }
Iftikhar Ali
la source
Quelle édition de SQL Server utilisez-vous? Je me demande si la réponse de @Peter Meinl doit être mise à jour pour refléter les modifications apportées dans 2008R2 et / ou Denali.
Yoopergeek
J'utilise SQL Server 2008 R2.
Iftikhar Ali
Je me demande si 2008 R2 se comporte mieux? La réponse de @hwiechers me fait également me demander si la version du Framework contre laquelle vous compilez empêche l'escalade. Enfin, je me demande si le fait d'être une instance R2 locale fait une différence. J'aimerais avoir le temps / les ressources pour enquêter sur la façon dont cela a changé avec la sortie de 2008 R2 et SQL Server 2012.
Yoopergeek
Vous ne savez pas si la connexion imbriquée est le problème? lol ... bien fleuri enlevez-le alors !, pourquoi diable les gens nichent-ils en utilisant des déclarations quand ce n'est pas absolument nécessaire, je ne saurai jamais.
Paul Zahra
1

TransactionScope dégénère toujours en transaction DTC, si vous utilisez l'accès à plus d'une connexion à l'intérieur. La seule façon dont le code ci-dessus peut fonctionner avec le DTC désactivé est si, par une énorme chance, vous obtenez la même connexion à partir du pool de connexions les deux fois.

"Le problème est que, sur la moitié de nos machines de développement, nous pouvons exécuter MSDTC désactivé." Êtes-vous sûr que c'est désactivé;)

amateur
la source
0

Assurez-vous que votre connectionString ne définit pas la mise en pool sur false. Cela se traduira par une nouvelle connexion pour chaque nouvelle SqlConnection dans le TransactionScope et l'escaladera en DTC.

FreddyV
la source