交易和MSDTC引起的困惑

9

我对事务和msdtc如何协同工作有一些基本的疑惑。

我有一个基本的服务器/客户端winforms应用程序。该应用程序使用transactionscope来封装在sql服务器上执行的多个sql命令。

当我仅在服务器上启用了msdtc网络访问时,该应用程序似乎可以正常工作。然后有一天它停止工作并显示网络访问未启用。

现在看来,为了使transactionscope正常工作,我必须在客户端计算机和服务器上都启用msdtc网络访问。

客户端或服务器的msdtc服务是谁做事务处理?或者可能是两者都有?

是否有任何指导,关于msdtc网络访问是否需要在客户端和服务器上都开启,还是只需要在服务器上开启?

3个回答

11
如果您正在使用MSDTC,则需要客户端(应用程序)和服务器(数据库)同时运行MSDTC,并且配置正确。这可能是一种痛苦的来源,特别是在处理防火墙时。如果您遇到问题,请参见“解决MSDTC问题”(Troubleshooting Problems with MSDTC),它虽然是针对BizTalk,但适用于MSDTC。 DTCPING也有帮助。
现在,如果您正在使用SQL Server 2005及更高版本,只访问一个数据库,使用一个数据库连接且不在应用程序域之间传递事务,则不需要使用MSDTC。在这些情况下,System.Transactions事务管理器将为您管理事务。如果出现任何前面提到的情况,则事务将升级为分布式事务(并且事务管理器将是MSDTC)。有关更多信息,请参见“事务管理升级”(Transaction Management Escalation)。
通常最好避免使用MSDTC,如果您不需要它。即如果您只处理单个SQL Server 2005+数据库,则尝试设计您的代码以不使用MSDTC为佳。除了配置麻烦外,DTC还会带来性能损失,因为所有对MSDTC的调用都是进程外的,并且使用了两阶段提交协议(MSDTC使用)的开销。
就您特定情况而言发生了什么很难说。如果您的代码没有更改,则可能是防火墙规则发生了变化?我也见过Windows更新更改DTC配置(出于安全原因)导致问题。

对于监视事务的晋升或升级,如果您没有使用任何分布式事务,我认为您可以使用一些分布式事务协调器性能计数器来跟踪已提交的事务。如果测试,您可以禁用 MSDTC 并查看您的代码是否失败。另一种方法是监视 SQL Server 中的事务。从编程角度出发,您可以尝试处理DistributedTransactionStarted 事件并进行一些日志记录(但在进入生产环境之前请删除该代码)。

有关使用单个连接的代码示例,请转到 MSDN 的TransactionScope 页面。基本上,创建一个 TransactionScope,创建一个 SqlConnection,使用 SqlConnection 进行一些工作,关闭连接,调用 scope.Complete()。

请注意,如果您使用 Data Adapter 方法,它们会自动管理您的连接,因此连接将关闭或返回到连接池。无论哪种方式,如果调用另一个操作,则事务将升级为 DTC 事务。有关更多详细信息,请参见System.Transactions 和连接池


感谢提供的有用信息。我希望不使用MSDTC。我正在使用sql2005,1个数据库,没有传递应用程序域,但不确定是否使用了1个连接。能否给出一个多个SQL语句的事务的简短示例,该示例不会升级到dtc?是否有工具或其他方法可以告诉我我的事务是否正在升级到msdtc? - muhan

8
为了进一步解释@Tuzo的说明,这里有一个总是升级的命令示例:
using(var scope = new TransactionScope())
{
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
  }
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
  }
  scope.Complete();
}

实际上,连接和命令将在另一个类中实现,但你可以理解这个概念。即使连接字符串是指向同一数据库,它也会使用DTC进行升级,因为它有两个连接。不进行升级的事务将如下所示:

using(var scope = new TransactionScope())
{
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
     //...some other command, etc.
  }
  scope.Complete();
}

这是更好的代码,因为你打开连接、完成所需操作并尽快关闭。这意味着你必须仔细考虑连接管理。根据你的应用程序,你可能会以不同的方式实现它。例如:

using(var scope = new TransactionScope())
using(var conn = new SqlConnection(connString))
{
    conn.Open();
    var myService = new MyService(conn);
    var myService2 = new MyService2(conn);
    myService.DoSomething();
    myService2.DoSomething();
    scope.Complete();
}

有多种方法可以实现这一点。企业库数据访问应用程序块和各种ORM也可以帮助您更有效地处理连接和事务。


1
企业库数据访问块可以帮助您,因为它维护了活动事务及其连接的内部列表(在TransactionScopeConnections类中)。同一连接用于事务的生命周期,因此事务不会升级为分布式事务。此外,它还具有使接口更清晰的好处,因为您不需要将连接传递给每个需要使用数据库连接的方法。 - Randy Levy
1
针对SQLserver 2008用户:你的代码的第一个示例不会在SQLserver 2008上升级。只有在打开第二个连接之前不关闭第一个连接时(例如,如果你将第三个using语句嵌套在第二个using语句中),它才会升级。 - mipe34

0

更新:我找到了一篇文章,解释了为什么在TransactionScope中仅使用GetData和Update来自同一个数据适配器时,事务会从LTM升级到MSDTC,并提供了一种解决方法。

关于TableAdapters + Transactions的权威博客文章 http://blah.winsmarts.com/2006/06/18/the-definitive-tableadapters--transactions-blog-post.aspx

我理解同时打开多个连接会导致事务分布式升级。然而,我遇到了一个问题,只有一个连接和一个针对数据库的查询也会导致事务升级。存储过程中也没有任何事务。如果有人知道原因,请告诉我。根据我的代码示例,“adapter.Update(table)”将触发分布式事务。

我已经将代码的核心从现有项目中提取出来并简化了大部分操作,但仍然遇到同样的问题。这基本上是使用表适配器创建数据集,并使用存储过程进行选择、插入和删除。我选择所有与特定用户相关的记录。然后,根据记录中是否存在“myPPID”,我添加或删除它。然后调用更新方法,并通过在组件服务中观察事务统计信息来看到事务升级为分布式。

我正在使用Windows XP Pro SP3和.NET Framework 3.5作为客户端程序。它通过局域网连接到SQL 2005数据库,该数据库位于Windows Server 2003 R2 Enterprise Edition SP2上。

private void button1_Click(object sender, EventArgs e)
{
int userId = 3;
int myPPId = 881;
using (TransactionScope ts = new TransactionScope())
{
    using (DataSet1TableAdapters.AssignedPPTableAdapter adapter 
    = new MSDTCPromotionTest.DataSet1TableAdapters.AssignedPPTableAdapter())
    {
        using (DataSet1.AssignedPPDataTable table = adapter.GetData(userId))
        {
            DataSet1.AssignedPPRow row = table.FindByUserIdmyPPId(
                userId, myPPId);
            if (row == null)
            {
                table.AddAssignedPPRow(userId, myPPId, string.Empty, 
                    string.Empty, true);
            }
            else
            {
                row.Delete();
            }
            adapter.Update(table);
        }
        ts.Complete();
    }
}
}

连接字符串并没有什么特别之处:

<add name="ConnectionString" connectionString="
Data Source=devdb;
Initial Catalog=&quot;TEST MSDTC&quot;;
Integrated Security=True"
providerName="System.Data.SqlClient" />

此外,存储过程是简单的CRUD调用。
创建:
ALTER procedure [dbo].[p_UserForm_AssignedPP_Insert]
(
    @UserId INT,
    @myPPId int
)
AS
SET NOCOUNT ON;
INSERT INTO [UsermyPP] ([UserID],[myPPID],[DateCreated])
     VALUES (@UserId,@myPPId,GETutcDATE()) 

阅读:

ALTER procedure [dbo].[p_UserForm_AssignedPP_SelectByUserId]
(
    @UserId int
)
AS
SELECT  
    [UserId],
    [myPPId], 
    '' Title,
    '' Abbreviation,
    0 IsArchived
from
    UsermyPP  unpp
where
    unpp.[userid] = @UserId

删除:
ALTER procedure [dbo].[p_UserForm_AssignedPP_Delete]
(
    @Original_UserId INT,
    @Original_MyPPId INT
)
AS
SET NOCOUNT ON;
DELETE FROM usermypp WHERE [UserID] = @Original_UserId 
    AND [MyPPID] = @Original_MyPPId

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