You're running a distributed transaction across SQL Server instances — maybe a linked server query or a two-phase commit in a COM+ component — and suddenly you hit XACT_E_INDOUBT (0X8004D016). The transaction status is in doubt. This isn't a permissions issue or a syntax error. It's a DTC (Distributed Transaction Coordinator) state problem. The culprit here is almost always a network blip, a power outage, or a forced restart that left the transaction hanging between prepared and committed.
Why This Happens
DTC uses a two-phase commit protocol. Phase one: everyone says "ready." Phase two: DTC says "commit." If the coordinator loses contact with a participant during phase two — or if the coordinator itself crashes after a prepare but before logging the final commit — the transaction goes in-doubt. The participant doesn't know whether to commit or roll back. It waits. And your app gets 0X8004D016.
The Real Fix: Manual Resolution
Skip restarting MSDTC — that rarely helps and can make things worse. You need to resolve the transaction manually. Here's the step-by-step for SQL Server. If you're using COM+, the approach is similar but uses Component Services snap-in instead.
- Identify the stuck transaction
On the SQL Server where you see the error, run this in SSMS:
Look for a transaction with state 3 (in-doubt). Note theSELECT * FROM sys.dm_tran_active_transactions WHERE transaction_begin_time < DATEADD(hour, -1, GETDATE());transaction_id. - Check DTC logs for the transaction
Open the MSDTC trace log (usuallyC:\Windows\System32\MSDTC\Trace\msdtc.log). You can also use the DTC console: rundcomcnfg, go to Component Services > Computers > My Computer > Distributed Transaction Coordinator > Transaction List. Find the stuck transaction by its UOW (Unit of Work) GUID. - Resolve the transaction
On the SQL Server, useKILLwith the UOW or transaction ID. But be careful — killing an in-doubt transaction can lead to data inconsistency if the transaction actually committed on the remote end. Safer approach: usesp_resolve_transaction.
You can also specifyEXEC sp_resolve_transaction @transaction_id = 'your-transaction-guid', @action = 'commit';'rollback'or'forget'if you're certain the transaction didn't complete. - If that fails, clear the transaction from DTC
Open an administrative PowerShell prompt and run:
This nukes the DTC log. All in-doubt transactions are forgotten. Only do this if you've verified no distributed transactions are pending on any participant. Otherwise you'll have orphaned transactions that never resolve.Stop-Service MSDTC -Force Remove-Item -Path "C:\Windows\System32\MSDTC\Trace\msdtc.log" -Force Start-Service MSDTC - Restart the SQL Server service
After cleaning DTC, restart SQL Server to force it to re-evaluate its transaction states. Use SQL Server Configuration Manager ornet stop MSSQLSERVERthennet start MSSQLSERVER.
What to Check If It Still Fails
- Firewall rules: MSDTC needs ports 135, 5000-5020 open between servers. Check both Windows Firewall and any third-party firewall.
- Network connectivity: Run a persistent ping between the two servers during a test transaction.
- DTC security settings: In Component Services, open the DTC properties. On the Security tab, make sure "Allow Remote Clients" and "Allow Inbound" and "Allow Outbound" are checked. Don't use "No Authentication" — use "Mutual Authentication Required" unless you have a cross-domain scenario.
- Event Viewer logs: Look under Applications and Services Logs > Microsoft > Windows > MSDTC for errors. You'll often see MSDTC Event ID 4159 or 4180 alongside this error.
One last thing: if this happens regularly, look at your network stability. A flaky NIC or a misconfigured load balancer is a common root cause. Fix that, and you'll stop seeing 0X8004D016 for good.