Skip to content
Microsoft SQL Server

SQL Server Error 266

Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT

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 266, Level 16, State 2, Procedure dbo.AdjustStock, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Other forms of this message
The procedure rolled back the caller's transaction
Msg 266, Level 16, State 2, Procedure dbo.AddOrder, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
Some sqlcmd builds (number in hexadecimal, no procedure name)
HResult 0x10A, Level 16, State 2
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Error number
266
Severity
16 (user-correctable error)
State
2
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 266 means a stored procedure finished with a different number of open transactions than it started with: it left a transaction open, or it committed or rolled back a transaction that belonged to its caller.

SQL Server records @@TRANCOUNT when a procedure starts and compares it when the procedure returns. If the two differ, it raises 266 at severity 16 against the procedure, with Line 0 because the check happens on exit rather than at a statement. The procedure's work is not undone by this error; it is a warning that the transaction structure is broken.

The two numbers tell you which way it went. Previous count = 0, current count = 1: the procedure began a transaction and returned without committing or rolling it back. Previous count = 1, current count = 0: the caller had a transaction open and the procedure ended it, almost always with a ROLLBACK in a CATCH block, because ROLLBACK without a savepoint name rolls back every level at once.

Common causes

  • A RETURN, or a GOTO, between BEGIN TRANSACTION and COMMIT, so one exit path skips the commit.
  • An error path that leaves the procedure without rolling back, while XACT_ABORT is OFF.
  • A CATCH block that runs ROLLBACK TRANSACTION while the caller had its own transaction open, which rolls back the caller's work too.
  • A procedure that commits a transaction it did not begin.

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.

How many transactions are open in this session?

SELECT @@TRANCOUNT AS trancount, XACT_STATE() AS xact_state;
What to look for

After 266 with current count = 1, trancount stays at 1 until you commit or roll back. Locks taken in that transaction are held meanwhile.

Find procedures that manage their own transactions

A quick text search to list candidates for review. Read each definition and check that every exit path commits or rolls back.

SELECT OBJECT_SCHEMA_NAME(object_id) + '.' + OBJECT_NAME(object_id) AS module_name,
       CASE WHEN definition LIKE '%ROLLBACK%' THEN 1 ELSE 0 END AS has_rollback,
       CASE WHEN definition LIKE '%XACT_STATE%' THEN 1 ELSE 0 END AS checks_xact_state
FROM sys.sql_modules
WHERE definition LIKE '%BEGIN TRAN%'
ORDER BY module_name;
What to look for

Procedures that begin transactions. Those with has_rollback = 1 but no savepoint or @@TRANCOUNT check are candidates for the 1 to 0 case.

Find sessions that were left with an open transaction

SELECT s.session_id, s.login_name, s.program_name, s.open_transaction_count,
       s.last_request_end_time
FROM sys.dm_exec_sessions AS s
WHERE s.open_transaction_count > 0
  AND s.is_user_process = 1;
What to look for

Idle sessions with open_transaction_count > 0 are often the result of a 0 to 1 mismatch that the application ignored.

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.

Give every exit path a COMMIT or ROLLBACK

Changes schema

Do the checks that can return early before BEGIN TRANSACTION, and wrap the transaction in TRY/CATCH so an error always ends in a rollback.

CREATE OR ALTER PROCEDURE dbo.AdjustStock @id INT, @qty INT
AS
BEGIN
    SET NOCOUNT ON;
    IF NOT EXISTS (SELECT 1 FROM dbo.Products WHERE id = @id) RETURN;   -- before the transaction
    BEGIN TRY
        BEGIN TRANSACTION;
        UPDATE dbo.Products SET stock = stock + @qty WHERE id = @id;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
END;

Only roll back the transaction the procedure started

Changes schema

If the caller already has a transaction, use a savepoint instead of a new transaction, roll back to the savepoint on error, and rethrow so the caller decides. If the procedure started the transaction, it rolls it back itself.

CREATE OR ALTER PROCEDURE dbo.AddOrder @id INT, @qty INT
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @own_tran BIT = CASE WHEN @@TRANCOUNT = 0 THEN 1 ELSE 0 END;
    IF @own_tran = 1 BEGIN TRANSACTION;
    ELSE SAVE TRANSACTION AddOrder;
    BEGIN TRY
        INSERT INTO dbo.Orders (id, qty) VALUES (@id, @qty);
        IF @own_tran = 1 COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @own_tran = 1 AND XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        ELSE IF XACT_STATE() = 1 ROLLBACK TRANSACTION AddOrder;
        THROW;
    END CATCH;
END;

Close the transaction the procedure left open

Changes data

Right after a 0 to 1 mismatch, the session still holds the transaction and its locks. Decide whether the work should be kept, then end it explicitly, and fix the procedure so it does not happen again.

IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;   -- or COMMIT, if the work is correct and complete

Examples

An early RETURN skips the COMMIT

SET NOCOUNT ON;
CREATE TABLE dbo.Products (id INT PRIMARY KEY, stock INT);
GO
CREATE PROCEDURE dbo.AdjustStock @id INT, @qty INT
AS
BEGIN
    BEGIN TRANSACTION;
    IF NOT EXISTS (SELECT 1 FROM dbo.Products WHERE id = @id)
        RETURN;   -- leaves the transaction open
    UPDATE dbo.Products SET stock = stock + @qty WHERE id = @id;
    COMMIT TRANSACTION;
END;
GO
EXEC dbo.AdjustStock @id = 99, @qty = 1;   -- product 99 does not exist
SELECT @@TRANCOUNT AS open_transactions;
Result
Msg 266, Level 16, State 2, Procedure dbo.AdjustStock, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.

open_transactions
-----------------
1

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

A CATCH block rolls back the caller's transaction

SET NOCOUNT ON;
CREATE TABLE dbo.Orders (id INT PRIMARY KEY, qty INT);
GO
CREATE PROCEDURE dbo.AddOrder @id INT, @qty INT
AS
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT INTO dbo.Orders (id, qty) VALUES (@id, @qty);
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;   -- rolls back every level, including the caller's
    END CATCH;
END;
GO
BEGIN TRANSACTION;
EXEC dbo.AddOrder 1, 5;
EXEC dbo.AddOrder 1, 7;   -- duplicate key: the CATCH block rolls back everything
SELECT @@TRANCOUNT AS open_transactions;
Result
Msg 266, Level 16, State 2, Procedure dbo.AddOrder, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.

open_transactions
-----------------
0

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

How to prevent it

Keep transaction control symmetrical: the code that runs BEGIN TRANSACTION also runs the matching COMMIT or ROLLBACK, on every path. Put early-exit checks before the transaction starts, and use one TRY/CATCH template for every procedure that writes.

For procedures that may be called inside another transaction, use the savepoint pattern above or leave transaction control entirely to the caller. Treat 266 as a defect, not a warning to suppress, because the 1 to 0 case silently discards the caller's work. Background: advanced stored procedures.

Common questions

Is the procedure's work rolled back when error 266 is raised?

No. Error 266 only reports the mismatch when the procedure returns. With current count = 1 the work is still in an open transaction waiting for COMMIT or ROLLBACK; with current count = 0 it was already rolled back (or committed) inside the procedure.

Why does ROLLBACK in my procedure affect the caller?

Nested BEGIN TRANSACTION statements only increase a counter; there is one real transaction. ROLLBACK without a savepoint name ends all of it. Use SAVE TRANSACTION and ROLLBACK TRANSACTION savepoint_name to undo only the procedure's part.

Why is the line number 0?

The check runs when the procedure exits, not at a particular statement, so SQL Server reports Line 0 against the procedure name.

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.