在 SQL Server 2005 上使用 System.Transactions 时出现 TransactionInDoubtException

发布于 2024-07-23 12:42:56 字数 3792 浏览 12 评论 0原文

这篇文章的根本问题是“为什么非促销的 LTM 交易会受到质疑?”

我收到 System.Transactions.TransactionInDoubtException 我无法解释原因。 不幸的是,我无法重现这个问题,但根据跟踪文件,它确实发生了。 我正在使用 SQL 2005,连接到一个数据库并使用一个 SQLConnection,因此我不希望发生升级。 错误消息表明超时。 然而,有时我会收到超时消息,但例外情况是事务已中止而不是有疑问,这更容易处理。

这是完整的堆栈跟踪:

System.Transactions.TransactionInDoubtException: The transaction is in doubt. ---> System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error)
   at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket()
   at System.Data.SqlClient.TdsParserStateObject.ReadBuffer()
   at System.Data.SqlClient.TdsParserStateObject.ReadByte()
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
   --- End of inner exception stack trace ---
   at System.Transactions.TransactionStateInDoubt.EndCommit(InternalTransaction tx)
   at System.Transactions.CommittableTransaction.Commit()
   at System.Transactions.TransactionScope.InternalDispose()
   at System.Transactions.TransactionScope.Dispose()

有什么想法吗? 为什么我会产生疑问?当我得到它时我应该做什么?

编辑以获取更多信息

实际上我仍然没有答案。 我确实意识到事务实际上部分提交了。 一个表获得插入,但另一个表没有获得更新。 代码被大量追踪,我没有太多遗漏的空间。

有没有办法可以轻松查出交易是否已推广。 我们可以从堆栈跟踪中判断是否是这样吗? 单阶段提交(位于轨道跟踪中)似乎表明对我来说没有晋升,但也许我错过了一些东西。 如果不升职,又怎么会受到质疑。

这个难题的另一个有趣的部分是我创建了当前事务的克隆。 我这样做是为了解决这个问题。 http://forums.microsoft.com/MSDN/ShowPost.aspx ?PostID=914869&SiteID=1

不幸的是,我不知道这个问题是否已经解决。 也许创建克隆会导致问题。 这是相关代码

using (TransactionScope ts = new TransactionScope())
{
   transactionCreated = true;
   //part of the workarround for microsoft defect mentioned in the beginning of this class
   Transaction txClone = Transaction.Current.Clone();
   transactions[txClone] = txClone;
   Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(TransactionCompleted);
   MyTrace.WriteLine("Transaction clone stored and attached to event");

   m_dataProvider.PersistPackage(ControllerID, package);
   MyTrace.WriteLine("Package persisted");
   m_dataProvider.PersistTransmissionControllerStatus(this);
   MyTrace.WriteLine("Transmission controlled updated");
   ts.Complete();
}

谢谢

The underlying question to this post is "Why would a non-promoted LTM Transaction ever be in doubt?"

I'm getting System.Transactions.TransactionInDoubtException and i can't explain why. Unfortunately i cannot reproduce this issue but according to trace files it does happen. I am using SQL 2005, connecting to one database and using one SQLConnection so i don't expect promotion to take place. The error message indicates a timeout. However, sometimes I get a timeout message but the exception is that the transaction has aborted as opposed to in doubt, which is much easier to handle.

Here is the full stack trace:

System.Transactions.TransactionInDoubtException: The transaction is in doubt. ---> System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error)
   at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket()
   at System.Data.SqlClient.TdsParserStateObject.ReadBuffer()
   at System.Data.SqlClient.TdsParserStateObject.ReadByte()
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
   --- End of inner exception stack trace ---
   at System.Transactions.TransactionStateInDoubt.EndCommit(InternalTransaction tx)
   at System.Transactions.CommittableTransaction.Commit()
   at System.Transactions.TransactionScope.InternalDispose()
   at System.Transactions.TransactionScope.Dispose()

Any ideas? Why am i getting in doubpt and what should i do when i get it?

EDIT for more information

I actually still don't have the answer for this. What I did realize is that the transaction actually partially commits. One table gets the insert but the other does not get the update. The code is HEAVILY traced and there is not much room for me to be missing something.

Is there a way I can easily find out if the transaction has been promoted. Can we tell from the stack trace if it is? SIngle Phase commit (which is in the strack trace) seems to indicate no promotion to me, but maybe i'm missing something. If its not getting promoted then how can it be in doubt.

Another interesting piece to the puzzle is that i create a clone of the current transaction. I do that as a workarround to this issue.
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=914869&SiteID=1

Unfortunately, i don't know if this issue has been resolved. Maybe creating the clone is causing a problem. Here is the relevant code

using (TransactionScope ts = new TransactionScope())
{
   transactionCreated = true;
   //part of the workarround for microsoft defect mentioned in the beginning of this class
   Transaction txClone = Transaction.Current.Clone();
   transactions[txClone] = txClone;
   Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(TransactionCompleted);
   MyTrace.WriteLine("Transaction clone stored and attached to event");

   m_dataProvider.PersistPackage(ControllerID, package);
   MyTrace.WriteLine("Package persisted");
   m_dataProvider.PersistTransmissionControllerStatus(this);
   MyTrace.WriteLine("Transmission controlled updated");
   ts.Complete();
}

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

绮筵 2024-07-30 12:42:56

目前公认的答案是,非促销 LTM(非 MSDTC)交易永远不会有疑问。 经过对类似问题的大量研究,我发现这是不正确的。

由于单阶段提交协议的实现方式,在事务管理器向其下属发送 SinglePhaseCommit 请求之后,以及下属回复已提交/提交之前,有一小段时间事务处于“不确定”状态。已中止/或已准备(需要升级/升级到 MSDTC)消息。 如果在此期间连接丢失,则该事务“有疑问”,因为 TransactionManager 在要求下级执行 SinglePhaseCommit 时从未收到响应。

来自 MSDN 单阶段提交 ,另请参阅此答案底部的“单阶段提交流程”图像:

这种优化可能有一个缺点:如果
事务管理者与下属参与者失去联系
在发送单阶段提交请求之后但在接收到
结果通知,它没有可靠的机制来恢复
交易的实际结果。 因此,本次交易
经理向任何申请或选民发送“有疑问”结果
等待信息结果通知

另外,这里还有一些我发现的导致 System.Transaction 升级/升级为 MSDTC 事务的实际示例(这与 OP 没有直接关系,但我发现非常有用。在 VS 2013 中进行了测试, SQL Server 2008 R2、.NET 4.5(除非另有说明):(

  1. 此特定于 SQL Server 2005 或兼容性级别 < 100)- 在 TransactionScope 内的任何点多次调用 Connection.Open()。 这还包括在 SAME 连接实例上调用 .Open()、.Close()、.Open()。
  2. 在 TransactionScope 中打开嵌套连接
  3. 使用不使用连接池的多个连接,即使它们不是嵌套的并且连接到同一数据库。
  4. 涉及使用 TransactionScope 的链接服务器
  5. SQL CLR 过程的查询。 请参阅:http://technet.microsoft.com/en-us/library/ms131084 .aspx “TransactionScope 仅应在访问本地和远程数据源或外部资源管理器时使用。这是因为 TransactionScope [在 CLR 内] 总是会导致事务提升,即使它仅在上下文连接中使用”
  6. 看来,如果使用连接池,并且连接 1 中使用的相同物理连接由于某种原因在连接“2 到 N”中不可用,则整个事务将被提升(b /c 这些被视为 2 个独立的持久资源,第 2 项是下面的 MS 官方列表)。 我还没有测试/确认这个特殊情况,但这是我对其工作原理的理解。 这在幕后是有意义的,这类似于使用嵌套连接或不使用连接池,因为使用了多个物理连接。 http://msdn.microsoft.com/en- us/library/8xx3tyca(v=vs.110).aspx “当连接关闭并通过登记的 System.Transactions 事务返回到池时,它会被搁置,以便下一个请求具有相同 System.Transactions 事务的连接池将返回相同的连接(如果可用)。 如果发出此类请求,并且没有可用的池连接,则会从池的非事务部分提取一个连接并登记。 "

以下是导致升级的 MS 官方列表:http://msdn.microsoft.com/en-us/library/ms229978(v=vs.85).aspx

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

单阶段提交流程

The current accepted answer is that a non-promoted LTM (non-MSDTC) Transaction can never be in doubt. After much research into a similar issue, I have found that this is incorrect.

Due to the way the single phase commit protocol is implemented, there is a small period of time where the transaction is "in doubt", after the Transaction Manager sends the SinglePhaseCommit request to its subordinate, and before the subordinate replies with either a committed/aborted/or prepared (needs to promote/escalate to MSDTC) message. If the connection is lost during this time, then the transaction is "in doubt", b/c the TransactionManager never received a response when it asked the subordinate to perform a SinglePhaseCommit.

From MSDN Single-Phase Commit, also see "Single phase commit flow" image at the bottom of this answer:

There is a possible disadvantage to this optimization: if the
transaction manager loses contact with the subordinate participant
after sending the Single-Phase Commit request but before receiving an
outcome notification, it has no reliable mechanism for recovering the
actual outcome of the transaction. Consequently, the transaction
manager sends an In Doubt outcome to any applications or voters
awaiting informational outcome notification

Also here are some practical examples of things I've found that cause System.Transaction promotion/escalation to a MSDTC transaction (this is not directly related to the OP, but I have found very useful. Tested in VS 2013, SQL Server 2008 R2, .NET 4.5 except where noted):

  1. (this one is specific to SQL Server 2005 or if compatibility level < 100)- Calling Connection.Open() more than once at any point within a TransactionScope. This also includes calling .Open(), .Close(), .Open() on the SAME connection instance.
  2. Opening nested connections within a TransactionScope
  3. Using multiple connections that do not use connection pooling, even if they are not nested and connecting to the same database.
  4. Queries that involve linked servers
  5. SQL CLR procedures that use TransactionScope. See: http://technet.microsoft.com/en-us/library/ms131084.aspx "TransactionScope should be used only when local and remote data sources or external resource managers are being accessed. This is because TransactionScope [within CLR] always causes transactions to promote, even if it is being used only within a context connection"
  6. It appears that if using connection pooling, and the same exact physical connection that was used in Connection1 is not available for some reason in Connections "2 to N" then the entire transaction will be promoted (b/c these are treated as 2 separate durable resources, item #2 is the MS official list below). I have not tested/confirmed this particular case, but is my understanding of how it works. It makes sense b/c behind the scenes this is similar to using nested connections or not using connection pooling b/c multiple physical connections are used. http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx "When a connection is closed and returned to the pool with an enlisted System.Transactions transaction, it is set aside in such a way that the next request for that connection pool with the same System.Transactions transaction will return the same connection if it is available. If such a request is issued, and there are no pooled connections available, a connection is drawn from the non-transacted part of the pool and enlisted"

And here is the MS official list of what causes escalation: http://msdn.microsoft.com/en-us/library/ms229978(v=vs.85).aspx

  1. At least one durable resource that does not support single-phase notifications is enlisted in the transaction.
  2. At least two durable resources that support single-phase notifications are enlisted in the transaction. For example, enlisting a single connection with SQL Server 2005 does not cause a transaction to be promoted. However, whenever you open a second connection to a SQL Server 2005 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.
  3. 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.

Single phase commit flow

落在眉间の轻吻 2024-07-30 12:42:56

答案是不能。 显然正在发生的事情是促销正在进行中。 (我们无意中发现了这一点)我仍然不知道如何检测是否正在发生促销尝试。 这对于检测这一点非常有用。

The answer is that it can't. What apparently was happening was that promotion was taking place. (We accidentally discovered this) I still don't know how to detect if a promotion attempt is happening. That would have been extreamly useful in detecting this.

天涯离梦残月幽梦 2024-07-30 12:42:56

如果不查看你的代码,很难给出任何建议,但我的第一个建议是,当你有 1 个 SQL 服务器和 1 个连接时,TransactionScope() 是一种开销。

为什么不使用 System.Data.SqlClient.SqlTransaction() 来代替?

文档称“如果在数据库事务中打开到远程服务器的连接,则到远程服务器的连接将被纳入分布式事务,并且本地事务将自动提升为分布式事务。” 然而,如果你真的只使用一个连接,那就是一个非常奇怪的错误。 您确定您没有调用任何可以创建与 MS SQL、MS MQ 或其他需要创建分布式事务的连接的第三方组件吗?

此外,如果您在 SQL Server CLR 过程中使用 TransactionScope(),无论如何它都会促进事务。

另外,如果您调用从链接的 SQL Server 访问表的存储过程,我想这也需要升级。

这个问题很老了,也许你已经知道答案并可以将其发布在这里供其他人使用。 谢谢!

Hard to advice anything without looking into your code, but my first suggestion is that TransactionScope() is an overhead when you have 1 SQL server with 1 connection.

Why not to use System.Data.SqlClient.SqlTransaction() instead?

Documentation sais that "If a connection to a remote server is opened within a database transaction, the connection to the remote server is enlisted into the distributed transaction and the local transaction is automatically promoted to a distributed transaction." However if you use really only one connection is a very strange error. Are you sure that you are not calling any 3rd party components that can create connections to MS SQL, MS MQ or something else that will require a distibuted transaction to be created?

Also if you use TransactionScope() in SQL Server CLR procedure, it will promote transaction in any case.

Also if you call a store procedure that access a table from linked SQL server, I suppose this will also require promotion.

The question is quite old, perhaps you already know the answer and could post it here for others. Thanks!

來不及說愛妳 2024-07-30 12:42:56

打败了我。

我习惯于手动对“BEGIN TRANSACTION”和“COMMIT”或“ROLLBACK”执行 ExecuteNonQuery。

很偶然的是,当某些代码需要无论是否在事务中都以相同的方式工作时,这种方法效果非常好。

Beats the heck out of me.

I'm in the habit of doing ExecuteNonQuery on "BEGIN TRANSACTION" and "COMMIT" or "ROLLBACK" by hand.

Quite by accident this worked out really well when some code needed to work just the same whether it was in a transaction or not.

兲鉂ぱ嘚淚 2024-07-30 12:42:56

我实际上遇到了同样的问题,它似乎与数据库服务器的规格有关。 当您执行此代码时,我会让您的 dba 查看一下机器的 CPU 利用率。 在我们的环境中会发生这种情况,因为我们正在事务中尝试对数据库中的大量行进行更新操作。 这种情况发生在我们最常用的表之一的 OLTP 数据库上,这将导致锁争用。 我发现这个问题令人着迷的是我在堆栈跟踪中看到的超时方面。 无论您设置什么超时值(无论是在命令上还是作为 TransactionScope 构造函数的参数),它似乎都无法解决问题。 我解决这个问题的方法是对提交进行分块。 希望这可以帮助

I am actually having the same problem and it seems to be related to the specs of the db server. I would have your dba have a look at the CPU utilization of the box while you are executing this code. This happens in our environment because we are attempting an update operation on a large number of rows in our database within a transaction. This is happening on our OLTP database on one of our most used tables which will create lock contention. What I find fascinating about the problem is the time out aspect which I see in your stack trace. No matter what time out values you set whether it be on the command or as an argument to the constructor of the TransactionScope it does not seem to adress the issue. The way I am going to address the issue is to chunk the commits. Hope this helps

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文