Fix ERROR_TRANSACTION_ALREADY_COMMITTED (0X00001A31)
This error means a transaction was already finalized when you tried to commit it again. Here's how to stop that from happening.
Quick Answer
If you see 0X00001A31, your code or app tried to commit a transaction that was already committed. The fix: find where a second COMMIT (or COMMIT TRANSACTION) runs after the first one, and remove or guard it with a check. In most cases, it's a mismatched BEGIN TRAN / COMMIT pair.
Why This Happens
This error pops up when you're working with SQL Server, ODBC, or any transactional system and you — or your code — calls COMMIT on a transaction that's already done. The system sees a transaction that's been finalized (either committed or rolled back) and a second commit request gets slapped with 0X00001A31. I've seen this most often in stored procedures that have multiple exit paths, or in apps that loop through transactions and accidentally commit the same one twice.
Last year, a client's nightly ETL job kept dying at 2 AM. The vendor's code had a BEGIN TRAN and then a COMMIT inside a loop, but also a COMMIT after the loop. That double-commit killed the whole script. Took me two hours to find it.
Step-by-Step Fix
- Find the transaction in your code. Look for
BEGIN TRANSACTIONorBEGIN TRANstatements. Then trace everyCOMMIT TRANSACTION,COMMIT,ROLLBACK TRANSACTION, andROLLBACKthat matches it. Count them: one BEGIN should have exactly one COMMIT or ROLLBACK — not two. - Check for explicit COMMITs in error handlers. Many stored procedures have a
COMMITin the success path and another in a CATCH block. If the success path runs, then the CATCH path also runs (because of a bug or a re-raise), you get a double commit. - Look for nested transactions. If you have
BEGIN TRANinside a loop, and youCOMMITeach iteration, make sure you're not also committing outside the loop. EachBEGIN TRANneeds its own counterpart. - Check for implicit commits. Some statements (like
ALTER DATABASE,CREATE TABLE, orBACKUP) can auto-commit an open transaction. If your code runs one of those mid-transaction and then tries toCOMMITafterward, you'll hit 0X00001A31. - Test with a transaction state query. In SQL Server, run
SELECT @@TRANCOUNTbefore and after each commit. If you see zero before a commit, that commit is redundant and will trigger the error.
When the Fix Isn't Straightforward
Sometimes the error comes from a third-party app or driver (like an old ODBC driver). In that case, you can't change the code. Your options:
- Update the driver. ODBC drivers for SQL Server prior to version 13 had bugs with transaction state mismanagement. Grab the latest from Microsoft.
- Use a wrapper. Wrap your calls in a
BEGIN TRAN ... COMMITblock at the application level, and suppress the second commit by checking@@TRANCOUNTbefore committing. - Isolate the transaction. If the error happens in a stored procedure you can't modify, call it inside its own transaction context using
SET XACT_ABORT ONand aTRY...CATCHblock that only commits once.
Prevention Tips
- Always use TRY...CATCH with a single COMMIT in the try and a ROLLBACK in the catch. No double paths.
- Use a flag. Set a variable like
@committed BIT = 0, set it to 1 after commit, and skip the commit if it's already 1. - Test with @@TRANCOUNT. Before any
COMMIT, checkIF @@TRANCOUNT > 0. If it's 0, you're committing nothing — skip it. - Avoid nested transactions unless you really know what you're doing. SQL Server treats them as counters, not true nesting. A ROLLBACK in an inner block can kill the outer.
Real story: A client had a PHP app that called a stored procedure twice for one request. The first call committed the transaction, the second hit 0X00001A31. Took me 15 minutes of checking Apache logs to see the double call. Fixed it by adding a session-level mutex.
Was this solution helpful?