关于事务和msdtc如何协同工作,我有一些基本的困惑。
我有一个基本的服务器/客户端winforms应用程序。该应用程序使用transactionscope来封装在sql server上执行的几个sql命令。
当我在服务器上启用msdtc网络访问时,应用似乎工作正常。然后有一天它停止工作,说没有启用网络访问。
现在看来我必须在客户端计算机和服务器上启用msdtc网络访问才能使transactioncope工作。
客户端或服务器msdtc服务是否可以进行事务处理?或者两者兼而有之?
是否有人就客户端和服务器或服务器上是否需要msdtc网络访问提供指导?
答案 0 :(得分:11)
如果您正在使用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()。
请注意,如果您使用的是数据适配器方法,它们会自动管理您的连接,以便关闭连接或将连接返回到连接池。无论哪种方式,如果调用另一个操作,那么事务将被提升为DTC事务。有关详细信息,请参阅System.Transactions and connection pooling。
答案 1 :(得分: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还可以帮助您更有效地处理连接和事务。
答案 2 :(得分: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”,我添加或删除它。然后,我调用update方法,通过观察组件服务中的Transaction Statistics来查看要分发的事务升级。
我正在使用Windows XP Pro SP3和.Net Framework 3.5作为客户端程序。它通过LAN连接到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="TEST MSDTC";
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