- 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
On this page
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; 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; 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; 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 schemaDo 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 schemaIf 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 dataRight 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;
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;
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
- @@TRANCOUNT (Transact-SQL)
- BEGIN TRANSACTION (Transact-SQL)
- ROLLBACK TRANSACTION (Transact-SQL)
- Database Engine events and errors 0 to 999
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.