0XC0190016

STATUS_TRANSACTION_ALREADY_COMMITTED (0XC0190016) Fix

This error means your database transaction was already finalized when you tried to roll it back. Here's how to fix the code and prevent it from happening again.

Quick answer for advanced users

You tried to roll back a transaction that was already committed. In C#, move the SqlTransaction.Rollback() call inside a catch block and ensure Commit() is only called after all operations succeed. In T-SQL, check @@TRANCOUNT before rolling back nested transactions.

I've seen this error pop up more times than I care to count, especially when folks are using explicit transactions in SQL Server 2019 or 2022 with C# code that mixes try-catch blocks with nested transaction logic. The core issue is simple: your application's transaction state machine got out of sync. You called Commit somewhere, then later—maybe in an error handler or cleanup routine—another part of the code tried to Rollback the same transaction. SQL Server slaps you with 0XC0190016 to say "sorry, that ship has sailed."

Why this happens

The most common real-world trigger is a C# method that starts a SqlTransaction, runs multiple SQL commands, and then somewhere in a finally block or a generic error handler, it calls transaction.Rollback() without checking if the transaction is still active. Another scenario is using nested transactions with SAVE TRAN in T-SQL, where an inner transaction gets committed but an outer rollback tries to undo it. I fought this error for three hours once because I had a stored procedure that used BEGIN TRAN inside a loop—when one iteration committed, the next loop's error handling tried to roll back the wrong scope.

Fix steps (C# with SqlTransaction)

  1. Restructure your try-catch-finally blocks. Only call Rollback() inside a catch block, never in finally. The finally block runs even after a successful commit, which causes this error.
    using (var connection = new SqlConnection(connectionString)){    connection.Open();    using (var transaction = connection.BeginTransaction())    {        try        {            // Execute your commands here            var command = new SqlCommand("UPDATE Accounts SET Balance = Balance - 100 WHERE AccountId = 1", connection, transaction);            command.ExecuteNonQuery();            // If all succeeds, commit            transaction.Commit();        }        catch        {            // Only roll back on failure            transaction.Rollback();            throw; // Re-throw or handle appropriately        }    }}
  2. Check transaction state before rollback in cleanup code. If you must use a finally block for resource cleanup, check if the transaction is still active using a flag or transaction.Connection != null. I prefer a boolean flag set to false after commit.
    bool committed = false;try{    // ... do work ...    transaction.Commit();    committed = true;}catch{    transaction.Rollback();    throw;}finally{    if (!committed && transaction.Connection != null)    {        transaction.Rollback();    }}

Alternative fix (T-SQL nested transactions)

If you're writing stored procedures and see this error inside SQL Server itself, you're likely dealing with nested BEGIN TRAN statements. The trick is to only roll back the outermost transaction and use SAVE TRAN for inner savepoints.

BEGIN TRY    BEGIN TRANSACTION;        -- Inner savepoint instead of nested transaction        SAVE TRANSACTION SavePoint1;        BEGIN TRY            -- Some risky operation            UPDATE Accounts SET Balance = Balance - 100 WHERE AccountId = 1;        END TRY        BEGIN CATCH            -- Roll back to savepoint, not whole transaction            ROLLBACK TRANSACTION SavePoint1;            -- Handle error, maybe log it        END CATCH        -- More operations        COMMIT TRANSACTION;END TRYBEGIN CATCH    IF @@TRANCOUNT > 0        ROLLBACK TRANSACTION;    -- Re-throw or log errorEND CATCH

Notice I check @@TRANCOUNT before rolling back. That's the safety net that stops 0XC0190016 cold.

Prevention tips

  • Never call Rollback() in a finally block. This is the number one cause. Use the try-catch pattern above.
  • Avoid explicit nested transactions. Use SAVE TRAN instead. SQL Server doesn't truly support nested transactions—each BEGIN TRAN increments a counter, but only the final COMMIT actually writes to disk. Rollback of any level rolls back everything.
  • Use a transaction scope library. In .NET, TransactionScope handles commit/rollback state automatically, which sidesteps this exact bug. Just wrap your code in using (var scope = new TransactionScope()) and call scope.Complete() only when successful.
  • Log transaction state in development. Add debug prints showing @@TRANCOUNT before and after each COMMIT/ROLLBACK. It's tedious, but it catches these state mismatches fast.

When the fix doesn't work

If you've restructured your code and still see the error, check for multiple threads accessing the same transaction object. SqlTransaction isn't thread-safe. One thread might commit while another attempts a rollback. Also look for event handlers or callbacks that fire asynchronously—they can trigger rollback after your main code path already committed. I once had a SqlInfoMessageEventHandler that was rolling back transactions in the background. Took me two days to trace that one.

That's the full picture. Fix the state machine, and 0XC0190016 will stop haunting your logs.

Related Errors in Database Errors
0XC0000215 STATUS_TRANSACTION_INVALID_TYPE (0xC0000215) Fix 0X00000125 STATUS_VOLSNAP_HIBERNATE_READY (0X00000125): What It Really Means Table 'wp_*' doesn't exist WordPress 'Table Doesn't Exist' After Plugin Install WriteConcernTimeout MongoDB Write Concern Timeout Exceeded – Fix It Fast

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.