Reason for System.Transactions.TransactionInDoubtException

I think this can happen also without MSDTC. I think I have had this happen in a system that did not use MSDTC at all. I think it is triggered if your DB connection fails at exactly a certain moment. The moment has to be such that the service has sent a COMMIT to the DB, but then the connection fails so that the service can't be sure if the DB ever received the COMMIT command or not.


Even if the transaction is local, transaction will still escalated to the MSDTC if you open multiple connections within the same transaction scope, according to this article: http://msdn.microsoft.com/en-us/library/ms229978(v=vs.110).aspx

An escalation that results in the System.Transactions infrastructure transferring the ownership of the transaction to MSDTC happens when: ...

  • 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.

NOTE: I have read some articles that state that this only applies to SQL 2005, and that SQL 2008+ is smarter about the MSDTC promotion. These state that SQL 2008 will only promote to MSDTC when multiple connections are open at the same time. See: TransactionScope automatically escalating to MSDTC on some machines?

Also, your inner exception is a Timeout (System.Data.SqlClient.SqlException: Timeout expired), not a Deadlock. While both are related to blocking, they are not the same thing. A timeout occurs when blocking causes the application to stop waiting on a resource that is blocked by another connection, so that the current statement can obtain locks on that resource. A deadlock occurs when two different connections are competing for the same resources, and they are blocking in a way they will never be able to complete unless one of the connections is terminated (this why the deadlock error messages say "transaction... has been chosen as the deadlock victim"). Since your error was a Timeout, this explains why you deadlock query returned a 0 count.

System.Transactions.TransactionInDoubtException from MSDN (http://msdn.microsoft.com/en-us/library/system.transactions.transactionindoubtexception(v=vs.110).aspx) states:

This exception is thrown when an action is attempted on a transaction that is in doubt. A transaction is in doubt when the state of the transaction cannot be determined. Specifically, the final outcome of the transaction, whether it commits or aborts, is never known for this transaction.

This exception is also thrown when an attempt is made to commit the transaction and the transaction becomes InDoubt.

The reason: something occurred during the TransactionScope that caused it's state to be unknown at the end of the transaction.

The cause: There could be a number of different causes, but it is tough to identify your specific cause without the source code being posted.

Things to check:

  1. If you are using SQL 2005, and more than one connection is opened, your transaction will be promoted to a MSDTC transaction.
  2. If you are using SQL 2008+, AND you have multiple connection open at the same time (i.e. nested connections or multiple ASYNC connections running in parallel), then the transaction will be promoted to a MSDTC transaction.
  3. If you have "try/catch{retry if timeout/deadlock}" logic that is running within your code, then this can cause issues when the transaction is within a System.Transactions.TransactionScope, because of the way that SQL Server automatically rolls back transaction when a timeout or deadlock occurs.

I once had this ""transaction is in doubt" when trying to complete a transaction scope that included calls to a database mapped via entity framework. One of the calls via entity framework was to a stored procedure that included a select statement with the command "with(tablock, holdlock)". The solution that seemed to work for me was to call Dispose() on the result returned by the stored procedure -- even thought the stored procedure just said "Return 0". This apparently freed up the resource.


To add to @BateTech's excellent answer in case this helps someone else, I had to debug exactly the same scenario just as he describes with multiple open async connections occurring in a method. The async calls were being individually awaited, but the method signature itself was 'async void' (!). The key thing about async is that if you're going to do it, you should do it from entry point all the way down.