9

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

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

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

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

客户端或服务器 msdtc 服务是否执行事务?或者两者兼而有之?

是否有人对客户端和服务器上是否需要 msdtc 网络访问或仅服务器有指导?

4

3 回答 3

11

如果您使用的是 MSDTC,那么您将需要客户端(您的应用程序)和服务器(数据库)来运行 MSDTC 并正确配置。

这可能是痛苦的根源,尤其是在处理防火墙时。如果您遇到问题,请参阅 MSDTC 故障排除问题。它谈到了 BizTalk,但它通常适用于 MSDTC。 DTCPING也是你的朋友。

现在,如果您使用 SQL Server 2005 及更高版本,仅访问一个数据库,使用一个数据库连接并且不在应用程序域之间传递事务,那么您不应该要求使用 MSDTC。在这种情况下,System.Transactions 事务管理器将为您管理事务。如果出现上述任何一种情况,那么事务将被提升为分布式事务(事务管理器将是 MSDTC)。有关详细信息,请参阅事务管理升级

一般来说,如果不需要,最好避免使用 MSDTC。即,如果您只处理单个 SQL Server 2005+ 数据库,那么请尝试将您的代码设计为不使用 MSDTC。除了配置麻烦之外,DTC 还会带来性能损失,因为对 MSDTC 的所有调用都在进程外,再加上两阶段提交协议(MSDTC 使用)的开销。

就您的特定情况下发生的事情而言,很难说。如果您的代码没有更改,那么防火墙规则可能已更改?我还看到 Windows 更新更改了导致问题的 DTC 配置(出于安全考虑)。

根据评论更新:

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

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

请注意,如果您正在使用数据适配器方法,它们会自动管理您的连接,以便连接关闭或返回到连接池。无论哪种方式,如果调用另一个操作,则该事务将被提升为 DTC 事务。有关更多详细信息,请参阅System.Transactions 和连接池

于 2009-10-14T06:39:22.013 回答
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 也可以帮助您更有效地处理连接和事务。

于 2009-10-14T14:11:39.980 回答
0

Update: I found an article that explains why transactions are being promoted form LTM to MSDTC when only usign both GetData and Update on the same data adapter within a TransactionScope along with a workaround.

The definitive TableAdapters + Transactions blog post http://blah.winsmarts.com/2006/06/18/the-definitive-tableadapters--transactions-blog-post.aspx

I understand the part about having multiple connections open at once escalating a transaction to be distributed. However, I'm having a problem where there is only one connection, and one query against a database that is escalating it. There are not any transactions in the stored procedure either. If anyone has a clue, I'd like to hear of it. From my code example, the "adapter.Update(table)" will trigger a distributed transaction.

I've brought the guts of the code out of my existing project and simplified most of what was going on, and I am still having the same problems. This is basically creating a dataset with a table adapter and setting it up with a stored procedure to select, insert, and delete. I select all related records with a specific user. Then, depending on if a "myPPID" exists for one of the records, I add it or delete it. I then call the update method and see the transaction escalate to be distributed by watching the Transaction Statistics in component services.

I'm using windows XP Pro SP3 and .Net Framework 3.5 for the client program. It connects to a SQL 2005 database over the LAN to 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();
    }
}
}

The connection string is nothing special:

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

Also, the stored procedures are simple crud calls.

Create:

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

Read:

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

Delete:

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
于 2010-05-14T16:27:11.577 回答