在某些机器上,TransactionScope自动升级到MSDTC?

292

在我们的项目中,我们使用TransactionScope来确保数据访问层在事务中执行它的操作。我们的目标是不需要在最终用户的机器上启用MSDTC服务。

问题是,在我们开发人员的一半机器上,我们可以禁用MSDTC运行。另一半必须启用它,否则他们会收到“MSDTC on [SERVER] is unavailable”错误消息。

这让我感到很困惑,并且让我认真考虑回滚到基于ADO.NET事务对象的自制TransactionScope解决方案。这似乎是疯狂的 - 在一半开发人员的同样代码上工作(并且不会升级)却会在另外一半开发人员的机器上升级。

我希望能找到更好的答案追踪事务何时会上升到DTC,但不幸的是这并没有给出答案。

这里有一小段代码示例,会导致问题,在尝试进行升级的机器上,它会在第二个connection.Open()上升级(是的,在此时没有其他连接打开。)

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();
}

我们真的深入研究并试图找出问题所在。以下是一些关于它能够运行的机器的信息:

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

无法运行的开发者机器:

  • Dev 4: Windows 7 x64, SQL2008 SQL2005
  • Dev 5: Windows Vista x86, SQL2005
  • Dev 6: Windows XP X86, SQL2005
  • 我的家用电脑: Windows Vista Home Premium, x86, SQL2005

需要补充的是,为了找出问题,所有机器都已经安装了来自Microsoft Update的所有可用补丁。

更新1:

这个MSDN事务升级页面说明了以下条件会导致事务升级到DTC:

  1. 至少一个不支持单阶段通知的持久性资源已经在事务中注册。
  2. 至少两个支持单阶段通知的持久性资源已经在事务中注册。例如,仅注册一个连接不会导致事务被提升。但是,每当您打开第二个连接到数据库并导致数据库注册时,System.Transactions基础架构检测到它是事务中的第二个持久性资源,并将其升级为MSDTC事务。
  3. 调用请求“编组”事务到不同的应用程序域或不同的进程。例如,跨应用程序域边界序列化事务对象。事务对象是按值进行编组的,这意味着尝试将其通过应用程序域边界传递(即使在同一进程中)会导致事务对象的序列化。您可以通过调用以Transaction作为参数的远程方法来传递事务对象,或者可以尝试访问远程事务服务组件。这将序列化事务对象并导致升级,就像将事务序列化到应用程序域中一样。它正在分布式,本地事务管理器不再足够。

我们没有遇到#3。#2没有发生,因为一次只有一个连接,并且也只有一个“持久性资源”。是否有任何方式可以发生#1?一些SQL2005/8配置导致它不支持单阶段通知吗?

更新2:

重新调查了每个人的SQL Server版本-“Dev 3”实际上有SQL2008,“Dev 4”实际上是SQL2005。这会教训我再也不相信我的同事了。;)由于数据的这种变化,我非常确定我们已经找到了问题所在。我们的SQL2008开发人员没有遇到问题,因为SQL2008包含了大量SQL2005没有的强大功能。
这也告诉我,因为我们将支持SQL2005,所以我们不能像之前一样使用TransactionScope,如果我们想要使用TransactionScope,我们需要传递单个SqlConnection对象...在SqlConnection无法轻松传递的情况下,这似乎是有问题的...它闻起来像是全局-SqlConnection实例。 Pew!
更新3
只是在这里澄清一下:
SQL2008:
允许在单个TransactionScope内使用多个连接(如上面示例代码中演示的)。
警告#1:如果这些多个SqlConnection嵌套,即同时打开两个或更多个SqlConnection,则TransactionScope将立即升级到DTC。
警告#2:如果打开了一个额外的SqlConnection到不同的“持久资源”(即:不同的SQL Server),它将立即升级到DTC。
SQL2005:
不允许在单个TransactionScope内使用多个连接,无论何时/如果第二个SqlConnection被打开,它都会升级。
更新4

为了使这个问题更加有用,也更加清晰明了,以下是如何使用单个SqlConnection将SQL2005升级到DTC的方法:

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

这对我来说似乎有些问题,但如果每次调用 SqlConnection.Open() 都从连接池中获取的话,我想我可以理解。

"不过,为什么会发生这种情况呢?" 好吧,如果在打开它之前使用 SqlTableAdapter 对该连接进行操作,则 SqlTableAdapter 将打开并关闭该连接,有效地为您完成事务,因为您现在无法重新打开它。

因此,基本上,为了成功地使用 SQL2005 的 TransactionScope,您需要拥有某种全局连接对象,该对象从第一个 TransactionScope 实例化时保持打开状态,直到不再需要为止。 除了全局连接对象的代码异味外,先打开连接再最后关闭它与尽可能晚地打开连接和尽早关闭连接的逻辑相矛盾。


2
“#2没有发生是因为一次只有一个连接” - #2并没有说第二个连接需要同时打开,只是需要在同一个事务中注册。 - Joe
3
非常感谢您提供第四次更新,展示了即使只使用单个SqlConnection也会出现升级问题。尽管我一直小心地确保只使用单个SqlConnection,但这正是我遇到的问题。很高兴知道是电脑疯了,而不是我的问题。 :-) - Oran Dennison
我在寻找异常原因时偶然发现了这个问题。我包含了异常详细信息,以便其他人可以找到它。分布式事务管理器(MSDTC)的网络访问已被禁用。内部异常为事务管理器已禁用其对远程/网络事务的支持0x8004D024。该软件在许多站点都能正常工作,但只有少数站点不能正常工作。结果他们正在运行SQL2005,并且正如这个问题所指出的那样,他们必须使用MSDTC。 - axeman
当我连接到SQLServer 2008并在ConnectionString中使用"pooling=false"时,出现了错误。整个下午,我的同事听到我喃喃自语:"但我三重检查了:我有2008年版本"。 - Mathias F
1
在同一事务范围内的嵌套连接将升级为分布式事务。从SQL Server 2008及以上版本开始,在同一事务范围内的多个(非嵌套)连接将不会升级为分布式事务。 - PreguntonCojoneroCabrón
显示剩余3条评论
7个回答

75

SQL Server 2008可以在一个TransactionScope中使用多个SQLConnection而不会升级,前提是这些连接没有同时打开,这样会导致多个“物理”TCP连接,从而需要升级。

我看到你们的开发人员有些使用的是SQL Server 2005,而另一些则使用SQL Server 2008。你确定已经正确识别哪些正在升级,哪些没有么?

最明显的解释是使用SQL Server 2008的开发人员不会升级。


是的,这些细节是正确的,有人真正查看代码吗?在事务范围内有两个连接,但在任何时刻只有一个连接实例化和打开。此外,没有在工作的机器上运行DTC。 - Yoopergeek
1
然而,在任何时刻只有一个连接实例和打开。这为什么很重要?使用SQL2005,如果您在事务范围内打开多个连接,则无论它们是否同时保持打开状态,都会升级。如果您考虑一下,这是合乎逻辑的。 - Joe
你和 hwiechers 现在让我开始怀疑,我很焦虑地想在周一上班时更仔细地检查他们的个人机器,并确保 SQL Server 版本与之前报告的一致。 - Yoopergeek
19
你和hwiechers都是正确的。我很难堪,谢谢你用提示棒敲打我。因为你第一个回答,所以你得到了答案。但我想澄清一点 - SQL2008允许打开多个连接,但不能同时打开。在任何时候仍只能有一个连接处于打开状态,否则TransactionScope将升级为DTC。 - Yoopergeek
@Yoopergeek 我可以确认你的“不同时”很重要,并相应地编辑了@Joe的答案。在测试时监视TCP连接显示,当连接不同时,旧的TCP连接将被重用,因此TransactionScope可以在服务器端使用单个COMMIT,这将使升级变得多余。 - Evgeniy Berezovsky

62

1
谢谢分享你的研究。它真的很有帮助。还有一个快速查询。TransactionScope()和sqlConnection.BeginTransaction()之间有什么区别? - Baig
根据这个功能请求,ODAC 12C现在应该像SQL 2008一样行为,当使用连续连接到相同的数据源时不会升级为分布式。 - Frédéric

33

当连接到2005时,那段代码将会导致升级。

请查看MSDN文档 - http://msdn.microsoft.com/en-us/library/ms172070.aspx

SQL Server 2008中可晋升的事务

.NET Framework 2.0和SQL Server 2005中,在TransactionScope内部打开第二个连接将自动将事务升级为完全分布式事务,即使两个连接使用相同的连接字符串。这种情况下,分布式事务增加了不必要的开销,降低了性能。

从SQL Server 2008和.NET Framework 3.5开始,如果在上一个事务关闭后的事务中打开了另一个连接,则本地事务不再升级为分布式事务。如果您已经使用连接池并在事务中注册,则无需更改代码。

我无法解释为什么Dev 3: Windows 7 x64,SQL2005成功,而Dev 4:Windows 7 x64失败。你确定这不是反过来吗?


如果您已经在使用连接池,我感到非常高兴能够引用这一细节。我是少数几位非常不幸的开发人员之一,因为无法使用连接池,所以我一直在苦苦挣扎,想弄清楚为什么在没有嵌套连接的情况下,在TransactionScope中使用SQL Server 2008+仍然会出现DTC错误。即使在对DTC提升进行详细分析时,也从未提到过连接池的要求。谢谢! - Pennidren

12

未找到http://msdn.microsoft.com/en-us/library/ms172153%28v=VS.80%29.aspx,Visual Studio 2005已退役文档。 - Kiquenet
这个错误发生在 SQL Server 2016 中。添加 enlist=false 确实解决了我的问题。 - Merin Nakarmi
Enlist=false 只是让所有的 TransactionScopes 失去作用,对吧?任何回滚都将无效。 - Patrick Szalapski

2

我不太确定嵌套连接是否是问题所在。我正在调用本地 SQL server 实例,它不会生成 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();
        }
    }

你使用的是哪个版本的SQL Server?我想知道@Peter Meinl的答案是否需要更新以反映在2008R2和/或Denali中所做的任何更改。 - Yoopergeek
我正在使用SQL Server 2008 R2。 - Iftikhar Ali
我想知道2008 R2是否表现更好?@hwiechers的回答也让我想知道您正在编译的框架版本是否防止了升级。最后,我想知道它是本地R2实例是否有任何区别。我希望我有时间/资源来调查随着2008 R2和SQL Server 2012的发布,这种情况如何改变。 - Yoopergeek
不确定嵌套连接是否是问题?哈哈...那就把它移除掉!为什么有些人在不绝对必要的情况下嵌套使用语句,我永远不会明白。 - Paul Zahra

1

如果您在TransactionScope内部使用超过1个连接,则它总是会升级为DTC事务。上述代码能够在禁用DTC的情况下工作的唯一方法是如果您有巨大的运气,两次都从连接池中获取同样的连接。

"问题是,在我们的一半开发人员的计算机上,我们可以禁用MSDTC并运行它。" 你确定确定它被禁用了吗;)


0

请确保您的connectionString未将pooling设置为false。否则,每个TransactionScope中的新SqlConnection都会导致一个新的连接,并将其升级到DTC。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接