Quick answer
If you're in a hurry: check the MSDTC logs to see which resource manager returned a heuristic commit, then manually reconcile that transaction so it matches the overall outcome.
Why this happens
This error trips up a lot of people, and I get it—it feels like the system is lying to you. You told the transaction coordinator to abort, and instead it committed. That's the essence of a heuristic outcome: one of the participants goes rogue and makes its own decision when the coordinator can't reach it in time. This usually happens during a network hiccup, a timeout, or when a resource manager like SQL Server or a message queue loses contact with the MSDTC (Microsoft Distributed Transaction Coordinator).
I've seen this most often in .NET applications using TransactionScope with a database and a message queue, or in classic COM+ components. The heuristic commit means the resource manager already applied the commit locally, so the transaction is now in a split-brain state. You can't just retry—you need to find out what committed and then align everything else.
Diagnose the culprit
First, fire up the Component Services console. Press Win + R, type dcomcnfg, and hit Enter. Expand Component Services → Computers → My Computer → Distributed Transaction Coordinator. Right-click Transaction List and choose View → Transaction Details. Look for any transaction with a status of Heuristically Committed or Heuristically Aborted. Double-click it to see which resource managers were involved. That's your starting point.
Fix step by step
- Check the MSDTC logs. Open Event Viewer (eventvwr.msc), go to Applications and Services Logs → Microsoft → Windows → MSDTC. Look for events with ID 4103 or 4181. They'll often name the resource manager and the transaction ID. Write that down.
- Identify the resource manager. If it's SQL Server, open SQL Server Management Studio and run
DBCC OPENTRANon the suspected database. You'll see active transactions and their status. If any showXACT_HEURISTIC, that's your culprit. - Manually reconcile the transaction. This is the ugly part. If the resource manager committed, you need to make the other participants match. For example, if the database committed but the message queue aborted, you'll need to re-enqueue the message or manually apply the change. There's no magic button here—you have to know what the transaction was supposed to do and fix the data by hand.
- Force a resolution via MSDTC. In the Component Services console, right-click the heuristically committed transaction and choose Resolve. You can pick Commit or Abort. But be careful—this just tells MSDTC to mark it as resolved. It doesn't undo the damage. Only do this after you've manually fixed the data.
- Restart MSDTC. After cleaning up, restart the service to clear any lingering state. Open an elevated command prompt and run
net stop msdtc && net start msdtc.
If the main fix doesn't work
Sometimes the transaction list is empty because the heuristic happened days ago and MSDTC already forgot it. In that case, you need to dig through your application logs. Check your .NET application's System.Transactions traces or your SQL Server error logs around the time of the failure. Look for the resource manager's resource manager ID—that GUID can be traced back to the specific component.
Another angle: if this is happening repeatedly, it's not a one-off network glitch. It's a configuration problem. Common culprits:
- Firewalls blocking RPC ports between the machines. MSDTC uses port 135 and a dynamic range (usually 49152-65535).
- Missing network DTC access. In the Component Services console, right-click My Computer → Properties → MSDTC tab → Security Configuration. Make sure Network DTC Access is checked, and that Allow Inbound and Allow Outbound are set appropriately for your environment.
- Timeout issues. If your transaction takes longer than the default 60-second timeout, you'll get this. Increase it in your code with
TransactionOptionsor in the machine.config if it's a COM+ component.
Prevention tips
The real fix is to avoid these outcomes altogether. That means:
- Use a reliable transaction coordinator. Make sure MSDTC is configured correctly on all machines. Test with the
dtcpingtool from the Windows SDK. - Set explicit timeouts. Don't rely on defaults. In .NET, set
TransactionScopewith a timeout that matches your actual business operation. - Consider alternatives. If you're constantly hitting this, maybe distributed transactions aren't the right pattern. Look into the Outbox pattern or sagas for microservices—they're more forgiving when things go sideways.
Heuristic commits are rare, but when they happen, they're a pain. The good news is that with a little digging, you can track down the offending resource manager and get your data back in sync. It's not fun, but it's doable.