在我们的项目中,我们使用 TransactionScope 来确保我们的数据访问层在事务中执行其操作。我们的目标是不要求在最终用户的计算机上启用 MSDTC 服务。
问题是,在我们一半的开发人员机器上,我们可以在禁用 MSDTC 的情况下运行。另一半必须启用它,否则他们会收到“[SERVER] 上的 MSDTC 不可用”错误消息。
这确实让我摸不着头脑,并让我认真考虑回滚到基于 ADO.NET 事务对象的类似 TransactionScope 的自制解决方案。这看起来很疯狂 - 在我们一半的开发人员上运行(并且不会升级)的相同代码确实在其他开发人员的上升级。
我希望对 追踪交易为何升级为 DTC 有更好的答案,但不幸的是事实并非如此。
一段会导致麻烦的代码示例,在尝试升级的计算机上,它会尝试在第二个连接上升级。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:
- 事务中至少登记了一个不支持单阶段通知的持久资源。
- 事务中至少登记了两个支持单阶段通知的持久资源。例如,登记单个连接不会导致事务被提升。但是,每当您打开与数据库的第二个连接导致数据库登记时,System.Transactions 基础结构就会检测到它是事务中的第二个持久资源,并将其升级为 MSDTC 事务。
- 调用将事务“编组”到不同应用程序域或不同进程的请求。例如,跨应用程序域边界的事务对象的序列化。事务对象是按值编组的,这意味着任何将其跨应用程序域边界传递的尝试(即使在同一进程中)都会导致事务对象的序列化。您可以通过调用以 Transaction 作为参数的远程方法来传递事务对象,也可以尝试访问远程事务服务组件。这会序列化事务对象并导致升级,就像跨应用程序域序列化事务一样。它正在被分发,本地事务管理器不再足够。
我们没有经历#3。 #2 没有发生,因为一次只有一个连接,而且它也连接到一个“持久资源”。有没有可能发生#1?某些 SQL2005/8 配置导致它不支持单阶段通知?
更新2:
重新调查一下,个人认为大家的SQL Server版本——“Dev 3”实际上是SQL2008,“Dev 4”实际上是SQL2005。这将教会我永远不要再相信我的同事。 ;) 由于数据的变化,我很确定我们已经找到了问题。我们的 SQL2008 开发人员没有遇到这个问题,因为 SQL2008 有大量 SQL2005 没有的很棒的功能。
它还告诉我,因为我们将支持 SQL2005,所以我们不能像以前那样使用 TransactionScope,并且如果我们想使用 TransactionScope,我们将需要传递一个 SqlConnection 对象......在 SqlConnection 不能轻易传递的情况下,这似乎是有问题的......它只是全局 SqlConnection 实例的味道。座位!
更新 3
只是为了澄清问题:
SQL2008:
- 允许单个 TransactionScope 内的多个连接(如上面的示例代码所示)。
- 警告#1:如果这些多个 SqlConnections 是嵌套的,即打开两个或多个 SqlConnections同时,TransactionScope将立即升级为DTC。
- 注意事项 #2:如果向不同的“持久资源”(即不同的 SQL Server)打开额外的 SqlConnection,它将立即升级到 DTC
SQL2005:
- 不允许在单个 TransactionScope 内建立多个连接, 时期。当/如果打开第二个 SqlConnection 时,它将升级。
更新 4
为了使这个问题变得更加一团糟有用,并且为了更清楚起见,这里介绍了如何使用单个将 SQL2005 升级到 DTC SqlConnection
:
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 实例化到不再需要它为止一直保持打开状态。除了全局连接对象的代码味道之外,首先打开连接并最后关闭它与尽可能晚打开连接并尽快关闭连接的逻辑相矛盾。
In our project we're using TransactionScope's to ensure our data access layer performs it's actions in a transaction. We're aiming to not require the MSDTC service to be enabled on our end-user's machines.
Trouble is, on half of our developers machines, we can run with MSDTC disabled. The other half must have it enabled or they get the "MSDTC on [SERVER] is unavailable" error message.
It's really got me scratching my head and has me seriously considering rolling back to a home-spun TransactionScope-like solution based on ADO.NET transaction objects. It's seemingly insane - the same code that works (and does not escalate) on half of our developer's does escalate on the other developer's.
I was hoping for a better answer to Trace why a transaction is escalated to DTC but unfortunately it doesn't.
Here's a sample bit of code that will cause the trouble, on the machines that try to escalate, it tries to escalate on the second connection.Open() (and yes, there is no other connection open at the time.)
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();
}
We've really dug in and tried to figure this out. Here's some info on the machines that it works on:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005 SQL2008
Developers it doesn't work on:
- Dev 4: Windows 7 x64,
SQL2008 SQL2005
- Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- My Home PC : Windows Vista Home Premium, x86, SQL2005
I should add that all machines, in an effort to hunt down the problem, have been fully patched with everything that's available from Microsoft Update.
Update 1:
That MSDN transaction-escalation page states that the following conditions will cause a transaction to escalate to DTC:
- At least one durable resource that does not support single-phase notifications is enlisted in the transaction.
- At least two durable resources that support single-phase notifications are enlisted in the transaction. For example, enlisting a single connection with does not cause a transaction to be promoted. However, whenever you open a second connection to a database causing the database to enlist, the System.Transactions infrastructure detects that it is the second durable resource in the transaction, and escalates it to an MSDTC transaction.
- A request to "marshal" the transaction to a different application domain or different process is invoked. For example, the serialization of the transaction object across an application domain boundary. The transaction object is marshaled-by-value, meaning that any attempt to pass it across an application domain boundary (even in the same process) results in serialization of the transaction object. You can pass the transaction objects by making a call on a remote method that takes a Transaction as a parameter or you can try to access a remote transactional-serviced component. This serializes the transaction object and results in an escalation, as when a transaction is serialized across an application domain. It is being distributed and the local transaction manager is no longer adequate.
We're not experiencing #3. #2 is not happening because there is only ever one connection at a time, and it's also to a single 'durable resource'. Is there any way that #1 could be happening? Some SQL2005/8 configuration that causes it to not support single-phase notifications?
Update 2:
Re-investigated, personally, everyone's SQL Server versions - "Dev 3" actually has SQL2008, and "Dev 4" is actually SQL2005. That'll teach me to never trust my coworkers again. ;) Because of this change in data, I'm pretty sure we've found our problem. Our SQL2008 developers weren't experiencing the problem because SQL2008 has copious amounts of awesome included that SQL2005 does not have.
It also tells me that because we're going to be supporting SQL2005 that we can't use TransactionScope like we have been, and if we want to use TransactionScope we're going to need to be passing a single SqlConnection object around...which seems problematic in situations where the SqlConnection can't easily be passed around...it just smells of global-SqlConnection instance. Pew!
Update 3
Just to clarify up here in the question:
SQL2008:
- Allows multiple connections within a single TransactionScope (as demonstrated in the above sample code.)
- Caveat #1: If those multiple SqlConnections are nested, that is, two or more SqlConnections are opened at the same time, TransactionScope will immediately escalate to DTC.
- Caveat #2: If an additional SqlConnection is opened to a different 'durable resource' (ie: a different SQL Server,) it will immediately escalate to DTC
SQL2005:
- Does not allow multiple connections within a single TransactionScope, period. It will escalate when/if a second SqlConnection is opened.
Update 4
In the interest of making this question even more of a mess useful, and just for more clarity's sake, here's how you can get SQL2005 to escalate to DTC with a single SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
This just seems broken to me, but I guess I can understand if every call to SqlConnection.Open()
is grabbing from the connection pool.
"Why might this happen, though?" Well, if you use a SqlTableAdapter against that connection before it's opened, the SqlTableAdapter will open and close the connection, effectively finishing the transaction for you because you now can't re-open it.
So, basically, in order to successfully use TransactionScope with SQL2005 you need to have some sort of global connection object that remains open from the point of the first TransactionScope is instantiated until it's no longer needed. Besides the code-smell of a global connection object, opening the connection first and closing it last is at odds against the logic of opening a connection as late as possible and closing it as soon as possible.
发布评论
评论(7)
SQL Server 2008 可以在一个
TransactionScope
中使用多个SQLConnection
,而无需升级,前提是这些连接不同时打开,否则会导致多个“物理”TCP 连接,并且因此需要升级。我看到你们的一些开发人员拥有 SQL Server 2005,其他开发人员拥有 SQL Server 2008。您确定已正确识别哪些正在升级,哪些没有?
最明显的解释是使用 SQL Server 2008 的开发人员没有升级。
SQL Server 2008 can use multiple
SQLConnection
s in oneTransactionScope
without escalating, provided the connections are not open at the same time, which would result in multiple "physical" TCP connections and thus require escalation.I see some of your developers have SQL Server 2005 and others have SQL Server 2008. Are you sure you have correctly identified which ones are escalating and which not?
The most obvious explanation would be that developers with SQL Server 2008 are the ones that aren't escalating.
我对该主题的研究结果:
请参阅 避免不必要的升级到分布式事务
我仍在调查 Oracle 的升级行为:
跨越多个连接到同一数据库的事务是否会升级为 DTC?
The result of my research on the topic:
See Avoid unwanted Escalation to Distributed Transactions
I am still investigating Oracle’s escalation behavior:
Do transactions spanning multiple connections to same DB escalate to DTC?
连接到 2005 时,该代码将导致升级。
请查看 MSDN 上的文档 - http://msdn.microsoft.com/en-us/library/ms172070.aspx
我无法解释为什么 Dev 3:Windows 7 x64、SQL2005 成功而 Dev 4:Windows 7 x64 失败。你确定这不是相反吗?
That code will cause an escalation when connecting to 2005.
Check the documentation on MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx
I can't explain why Dev 3: Windows 7 x64, SQL2005 succeeds and Dev 4: Windows 7 x64 fails. Are you sure that is not the other way round?
我不知道为什么这个答案被删除,但这似乎有一些相关信息。
于 2010 年 8 月 4 日 17:42 回答 Eduardo
在连接上设置 Enlist=false字符串以避免在事务上自动登记。
手动将连接登记为参与者交易范围内。 [原始文章已过时]
或者执行以下操作:如何防止自动 MSDTC 升级 [archive.is]
I don't know why this answer was deleted but this seems to have some relevant information.
answered Aug 4 '10 at 17:42 Eduardo
Set Enlist=false on connection string to avoid auto enlistment on transaction.
Manually enlist connection as participants in transaction scope. [original article outdated]
or do this: How to prevent automatic MSDTC promotion [archive.is]
我不太确定嵌套连接是否是问题所在。我正在调用 SQL Server 的本地实例,但它没有生成 DTC?
I'm not too sure if nested connection is the issue. I'm calling a local instance of SQL server and it doesn't generate the DTC??
如果您在内部使用超过 1 个连接的访问,TransactionScope 始终会升级为 DTC 事务。上面的代码可以在禁用 DTC 的情况下工作的唯一方法是,如果您很可能两次从连接池中获得相同的连接。
“问题是,在我们一半的开发人员机器上,我们可以在禁用 MSDTC 的情况下运行。”
您确定它已被禁用吗;)
TransactionScope always escalates to DTC transaction, if you use access more than 1 connection inside. The only way the code above can work with DTC disabled is if by a huge chance you get the same connection from the connection pool both times.
"Trouble is, on half of our developers machines, we can run with MSDTC disabled."
Are you sure sure it's disabled ;)
确保您的连接字符串未将池设置为 false。这将导致 TransactionScope 中的每个新 SqlConnection 产生一个新连接,并将其升级为 DTC。
Make sure your connectionString does not set pooling to false. This will result in a new connection for each new SqlConnection in the TransactionScope and escalates it to DTC.