InletDownload

SQL Server error 3930

The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.

An earlier error doomed your transaction: it can only be rolled back. Error 3930 means you tried to write or commit in it anyway; 3998 means a batch ended with it still open, so SQL Server rolled it back. In CATCH, check XACT_STATE() and roll back before doing anything else.

The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.

Tested on SQL Server 2022 (16.0.4295.3); also 2019 (15.0.4490.9) and 2025 (17.0.5005.3) · Updated 9 October 2026

What it means

Some errors leave a transaction doomed (Microsoft’s word is uncommittable): it’s still open and still holds its locks, but the only thing it can do is roll back. Two errors follow from that:

  • 3930 “The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.” You ran a COMMIT, or a statement that writes (an INSERT into a log table, say), inside a doomed transaction.
  • 3998 “Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.” A batch finished with a doomed transaction still open, so SQL Server rolled it back for you.

Neither is the real problem. The real problem is the error before them, the one that doomed the transaction, and your CATCH block (or the code after it) has hidden it.

XACT_STATE() tells you where you are:

XACT_STATE()MeaningWhat you can do
1A transaction is open and can commitCommit, roll back, or keep going
0No transactionNothing to roll back
-1A transaction is open but doomedRoll back only

Common causes

  1. SET XACT_ABORT ON plus any error inside TRY. With XACT_ABORT on, every run-time error dooms the transaction, so the CATCH block always starts at XACT_STATE() = -1.
  2. An error that aborts the batch, inside TRY, even with XACT_ABORT off: a conversion error such as Conversion failed, a deadlock (1205), and others that would normally end the batch. Inside TRY they jump to CATCH with the transaction doomed.
  3. A CATCH block that logs first and rolls back second. It writes the error to a table while the transaction is doomed: 3930.
  4. A CATCH block that swallows the error without rolling back, so the batch ends with the doomed transaction open: 3998.
  5. Triggers, where XACT_ABORT is on by default, so an error inside one behaves as in cause 1.

How to fix it

Roll back first, then do the rest

In CATCH, roll back before anything that writes, keep the error details in variables, and log after the rollback, outside the transaction:

SET XACT_ABORT ON;
BEGIN TRY
  BEGIN TRANSACTION;
  UPDATE accounts SET balance = balance - 10 WHERE id = 1;
  INSERT INTO orders (id, account_id, total, status) VALUES (1, 1, 10.00, 'open');
  COMMIT;
END TRY
BEGIN CATCH
  IF XACT_STATE() <> 0 ROLLBACK;            -- first, whatever the state
  INSERT INTO error_log (number, message)    -- now outside any transaction
  VALUES (ERROR_NUMBER(), ERROR_MESSAGE());
  THROW;                                     -- re-raise the original error
END CATCH;

ERROR_NUMBER() and ERROR_MESSAGE() still work after the rollback, anywhere in the CATCH block. THROW without arguments re-raises the original error, so the caller sees the duplicate key (or whatever it was) instead of a vague 3930.

Use XACT_ABORT ON, and expect -1

Turning XACT_ABORT on is the usual advice: any run-time error rolls back the whole transaction, so you can’t commit half a unit of work by accident. The price is that the CATCH block always sees a doomed transaction, which is fine if it rolls back first. (Compile errors, such as a misspelt column, aren’t affected by XACT_ABORT: the batch never starts.)

Only commit when XACT_STATE() is 1

If you want to keep a transaction going after an error you expected (with XACT_ABORT off and a statement-level error such as a duplicate key), check first:

BEGIN CATCH
  IF XACT_STATE() = 1
    COMMIT;        -- or carry on: the transaction is still good
  ELSE IF XACT_STATE() = -1
    ROLLBACK;
END CATCH;

Don’t use @@TRANCOUNT for this: it’s 1 for a doomed transaction too.

Log what doomed it

When you only see 3930 or 3998 in an application log, the original error went into a CATCH block that didn’t re-raise it. Add THROW; (or log ERROR_NUMBER(), ERROR_MESSAGE() and ERROR_LINE()) in that block to find the real error.

Reproduce it

On SQL Server 2022 (RTM-CU27, 16.0.4295.3), with seo_sqlerr_conn.orders already holding a row with id 1, and an error_log table:

SET XACT_ABORT ON;
BEGIN TRY
  BEGIN TRAN;
  UPDATE seo_sqlerr_conn.accounts SET balance = balance - 10 WHERE id = 1;
  INSERT INTO seo_sqlerr_conn.orders (id, account_id, total, status) VALUES (1, 1, 10.00, 'open');
  COMMIT;
END TRY
BEGIN CATCH
  PRINT CONCAT('caught ', ERROR_NUMBER(), ', XACT_STATE() = ', XACT_STATE());
  INSERT INTO seo_sqlerr_conn.error_log (number, message) VALUES (ERROR_NUMBER(), ERROR_MESSAGE());
  ROLLBACK;
END CATCH;
caught 2627, XACT_STATE() = -1
Msg 3930, Level 16, State 1, Server 7732422b7f56, Line 11
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.

The duplicate key (2627) doomed the transaction; the INSERT into the log table got 3930; with XACT_ABORT on, that error ended the batch and rolled the transaction back (@@TRANCOUNT was 0 afterwards), so the ROLLBACK line never ran. A COMMIT in the same place got the same 3930.

With XACT_ABORT off, a conversion error and a CATCH block that only prints:

BEGIN TRY
  BEGIN TRAN;
  UPDATE seo_sqlerr_conn.accounts SET balance = balance - 10 WHERE id = 1;
  SELECT CAST('abc' AS int);
  COMMIT;
END TRY
BEGIN CATCH
  PRINT CONCAT('caught ', ERROR_NUMBER(), ', XACT_STATE() = ', XACT_STATE());
END CATCH;
caught 245, XACT_STATE() = -1
Msg 3998, Level 16, State 1, Server 7732422b7f56, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

The same duplicate key with XACT_ABORT off left the transaction committable (XACT_STATE() = 1), and the log-table INSERT worked, but the ROLLBACK after it undid that row too: one more reason to roll back first and log afterwards. SQL Server 2019 (15.0.4490.9) and 2025 (17.0.5005.3) gave the same results.

The fixed CATCH block from above (roll back, log, THROW) on the same duplicate key returned the original error instead of 3930, kept the log row, and left the balance unchanged:

Msg 2627, Level 14, State 1, Server 7732422b7f56, Line 6
Violation of PRIMARY KEY constraint 'PK__orders__3213E83F9CA27086'. Cannot insert duplicate key in object 'seo_sqlerr_conn.orders'. The duplicate key value is (1).

In Inlet

Query tabs run T-SQL a batch at a time (GO splits batches) and show PRINT messages with the results, so you can step through a TRY…CATCH block like the ones above and see XACT_STATE() at each point. Edits you make in the grid are staged until you commit (⌘S), and Review shows the statements that will run first. When a statement fails with 3930 or 3998, the error links to this page.

Related

Sources