Skip to content
Microsoft SQL Server

SQL Server Error 3930

The current transaction cannot be committed (doomed transaction)

Severity 16 Transactions
Applies to: All supported SQL Server versions (2016 to 2025), Azure SQL Database, Azure SQL Managed Instance
Last technically reviewed 23 October 2026 on SQL Server 2025 (17.0.1145.4)
The full message
Msg 3930, Level 16, State 1, Line 1
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Other forms of this message
Often follows at the end of the batch (when XACT_ABORT is OFF)
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.
Error number
3930
Severity
16 (user-correctable error)
State
1
Product
Microsoft SQL Server Database Engine
Versions
All supported SQL Server versions (2016 to 2025), Azure SQL Database, Azure SQL Managed Instance
Category
Transactions
Last reviewed
23 October 2026

What it means

Error 3930 means an earlier error left the transaction uncommittable (doomed), and the code then tried to commit it or write more data inside it; the only thing it can still do is roll back.

Inside a TRY block, some errors do not just fail the statement, they doom the whole transaction: every error when SET XACT_ABORT ON is in effect, and with it OFF, errors that would otherwise end the batch, such as a conversion failure. A doomed transaction still holds its locks, but it can only read and roll back. Any INSERT, UPDATE, DELETE, or a COMMIT, raises 3930 at severity 16.

The function that tells you where you stand is XACT_STATE(): 1 means there is an active transaction that can be committed, -1 means there is a transaction but it is doomed, and 0 means there is no transaction. @@TRANCOUNT cannot tell 1 and -1 apart, which is why CATCH blocks that test only @@TRANCOUNT and then commit or log run into this error.

Common causes

  • A CATCH block writes to a log table before rolling back, while the transaction is already doomed.
  • A CATCH block decides to commit because @@TRANCOUNT is greater than 0, without checking XACT_STATE().
  • SET XACT_ABORT ON in the session or procedure, which makes every error in a TRY block doom the transaction, including duplicate keys and constraint violations.
  • An error such as a failed conversion (error 245) inside the TRY block, which dooms the transaction even with XACT_ABORT OFF.

Diagnose it

Read-only These queries only read metadata and dynamic management views. They do not change data, permissions or configuration, so they are safe to run first.

Ask SQL Server whether the transaction can still be committed

Run this in the CATCH block (or right after the failure in the same session).

SELECT XACT_STATE() AS xact_state,   -- 1 committable, -1 doomed, 0 none
       @@TRANCOUNT  AS trancount;
What to look for

xact_state = -1 confirms a doomed transaction: roll back before doing anything else.

Is XACT_ABORT ON for this session?

SELECT CASE WHEN (@@OPTIONS & 16384) = 16384 THEN 'ON' ELSE 'OFF' END AS xact_abort;
What to look for

ON means any error inside the TRY block dooms the transaction, so the CATCH block must always roll back.

Read the original error in the CATCH block

3930 is a symptom. The error that doomed the transaction is the one to fix; capture it before anything else in the CATCH block.

SELECT ERROR_NUMBER() AS error_number, ERROR_MESSAGE() AS error_message,
       ERROR_PROCEDURE() AS error_procedure, ERROR_LINE() AS error_line;
What to look for

Inside a CATCH block: the first error, for example 2627 or 245. Outside a CATCH block all four columns are NULL.

How to fix it

Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.

Roll back first in every CATCH block, then log and rethrow

Changes data

Test XACT_STATE(), roll back if there is any transaction, and only then write to the log table: after the rollback the insert runs outside the doomed transaction. THROW passes the original error to the caller.

SET XACT_ABORT ON;
BEGIN TRY
    BEGIN TRANSACTION;
    INSERT INTO dbo.Orders (id, qty) VALUES (1, 7);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
    INSERT INTO dbo.ErrorLog (message) VALUES (ERROR_MESSAGE());
    THROW;
END CATCH;

Commit only when XACT_STATE() is 1

Changes data

If your CATCH block is meant to keep the work done so far (only possible with XACT_ABORT OFF and an error that did not doom the transaction), commit when the state is 1 and roll back when it is -1.

BEGIN CATCH
    IF XACT_STATE() = 1  COMMIT TRANSACTION;     -- still committable
    IF XACT_STATE() = -1 ROLLBACK TRANSACTION;   -- doomed: roll back
END CATCH;

Keep error details in a table variable across the rollback

Changes data

Rows in a table variable are not removed by ROLLBACK. Collect details there while the transaction is doomed, roll back, then save them to the permanent log.

BEGIN CATCH
    DECLARE @err TABLE (number INT, message NVARCHAR(4000));
    INSERT INTO @err VALUES (ERROR_NUMBER(), ERROR_MESSAGE());
    IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
    INSERT INTO dbo.ErrorLog (message) SELECT message FROM @err;
    THROW;
END CATCH;

Examples

Logging the error inside a doomed transaction (XACT_ABORT ON)

SET NOCOUNT ON;
CREATE TABLE dbo.Orders (id INT PRIMARY KEY, qty INT);
CREATE TABLE dbo.ErrorLog (logged_at DATETIME2 DEFAULT SYSDATETIME(), message NVARCHAR(4000));
GO
SET XACT_ABORT ON;
BEGIN TRY
    BEGIN TRANSACTION;
    INSERT INTO dbo.Orders VALUES (1, 5);
    INSERT INTO dbo.Orders VALUES (1, 7);   -- duplicate key dooms the transaction
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- XACT_STATE() is -1 here: the transaction is doomed
    INSERT INTO dbo.ErrorLog (message) VALUES (ERROR_MESSAGE());   -- a write: not allowed
END CATCH;
Result
Msg 3930, Level 16, State 1, Line 10
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.

Reproduced on SQL Server 2025 (17.0.1145.4), 23 October 2026.

Committing after a conversion error (XACT_ABORT OFF)

SET NOCOUNT ON;
CREATE TABLE dbo.Orders (id INT PRIMARY KEY, qty INT);
GO
BEGIN TRY
    BEGIN TRANSACTION;
    INSERT INTO dbo.Orders VALUES (1, 5);
    INSERT INTO dbo.Orders VALUES (2, CAST('five' AS INT));   -- error 245 dooms the transaction
END TRY
BEGIN CATCH
    -- here XACT_STATE() = -1 but @@TRANCOUNT = 1
    IF @@TRANCOUNT > 0 COMMIT TRANSACTION;   -- @@TRANCOUNT cannot see the transaction is doomed
END CATCH;
Result
Msg 3930, Level 16, State 1, Line 8
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

Reproduced on SQL Server 2025 (17.0.1145.4), 23 October 2026.

How to prevent it

Use one CATCH block pattern everywhere: IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; as the first statement, then logging, then THROW. Many teams also put SET XACT_ABORT ON at the top of procedures that open transactions, so a client timeout or any error never leaves a transaction open; with that setting, rolling back in the CATCH block is mandatory.

Never decide to commit from @@TRANCOUNT alone. For a refresher on how transactions and rollbacks behave, see SQL transactions and ACID explained.

Common questions

What is a doomed transaction in SQL Server?

A transaction that an error inside a TRY block has made uncommittable. It still exists and holds its locks, XACT_STATE() returns -1, and the only allowed actions are reads and ROLLBACK.

What is the difference between XACT_STATE() and @@TRANCOUNT?

@@TRANCOUNT counts open BEGIN TRANSACTION levels. XACT_STATE() returns 1, 0 or -1 and is the only one that tells you whether the open transaction can still be committed.

Why do I also get error 3998 after 3930?

When the batch ends with a doomed transaction still open, SQL Server rolls it back for you and reports 3998. Rolling back in your CATCH block avoids both messages.

Learn more on CodeWithSQL

Official documentation

Last technically reviewed 23 October 2026; examples and diagnostics run on SQL Server 2025 (17.0.1145.4). Message text checked against the engine's sys.messages catalogue.

Still stuck on a SQL Server error?

Browse the full SQL Server error library, or get hands-on help from an experienced SQL professional.