What is IMPLICIT_TRANSACTIONS?
By default, SQL Server runs in autocommit mode:
SELECT * FROM Employees; -- Committed immediately
INSERT INTO Orders VALUES(...); -- Committed immediately
DELETE FROM LogTable WHERE...; -- Committed immediately
Each statement is wrapped in its own transaction and committed automatically.
SET IMPLICIT_TRANSACTIONS ON changes this behavior. Now, a transaction starts implicitly when you execute any of these statements:
SELECT, INSERT, UPDATE, DELETE, MERGE,
GRANT, REVOKE, CREATE, ALTER, DROP,
FETCH, OPEN, CLOSE, ...
(and many others)
Once a transaction starts, you must explicitly commit or rollback:
SET IMPLICIT_TRANSACTIONS ON;
SELECT * FROM Employees; -- Starts transaction implicitly
INSERT INTO Orders VALUES(...); -- Same transaction continues
-- Transaction is still open!
COMMIT; -- Now it commits
If you forget to commit, locks hold, connections hang, and data sits in limbo.
Why It Exists (Brief History)
IMPLICIT_TRANSACTIONS was added for compatibility with other databases (like Oracle) that don't use autocommit by default. Some legacy applications expected this behavior and couldn't be rewritten.
In 1990, it made sense. In 2024, it's a footgun masquerading as a feature.
The Problems
Problem 1: Forgotten Commits
The most common issue: developers forget to commit, and locks spiral.
Developer A runs an UPDATE statement in SSMS to test a query.
IMPLICIT_TRANSACTIONS is ON (maybe set in a startup script).
Developer A forgets to type COMMIT.
Session stays open. Locks acquired on tables remain locked.
Other developers try to UPDATE those tables. They block. They wait. They assume the server is slow.
After 2 hours, someone notices the blocking and kills the session.
This happens so frequently in shops with IMPLICIT_TRANSACTIONS that DBAs spend hours investigating "blocking" issues that don't exist.
Problem 2: Unexpected Transaction Scope
Developers assume a statement commits automatically. They don't expect it to participate in a transaction:
SET IMPLICIT_TRANSACTIONS ON;
BEGIN TRY
INSERT INTO Orders (OrderID, CustomerID) VALUES (1001, 5);
INSERT INTO OrderDetails (OrderID, LineNo, Qty) VALUES (1001, 1, 10);
-- Developer expects both to be committed here
-- But they're not! Transaction is still open.
-- ... more code that assumes data is committed...
ROLLBACK; -- Oops! Rollbacks both INSERTs
END TRY
BEGIN CATCH
ROLLBACK;
END CATCH;
COMMIT; -- This doesn't hurt, but it's redundant
The developer thinks the INSERTs are committed after the statements execute. They're not. They're stuck in a transaction waiting for COMMIT.
Problem 3: Inconsistent Behavior Across Frameworks
Different database drivers and ORMs handle IMPLICIT_TRANSACTIONS differently:
- SSMS: IMPLICIT_TRANSACTIONS might be ON (depends on startup script)
- Python (pyodbc): Usually ignores IMPLICIT_TRANSACTIONS, uses autocommit
- C# (ADO.NET): Respects it
- Entity Framework: Wraps queries in explicit transactions anyway
- Node.js (mssql): Depends on connection settings
The same code works in SSMS but fails in production because the application layer doesn't expect IMPLICIT_TRANSACTIONS.
Problem 4: SELECT Statements Create Transactions
Even a simple SELECT starts a transaction:
SET IMPLICIT_TRANSACTIONS ON;
SELECT COUNT(*) FROM Orders; -- Opens a transaction!
-- Table is now locked at isolation level
-- Other writers block
COMMIT; -- Release locks
This is insane. Reading data should never hold locks unless you explicitly ask for it. But with IMPLICIT_TRANSACTIONS, every SELECT holds a lock until you commit.
Problem 5: Breaks Cursor Logic
Cursors have their own transaction semantics. IMPLICIT_TRANSACTIONS interferes:
SET IMPLICIT_TRANSACTIONS ON;
DECLARE @id INT;
DECLARE cur CURSOR FOR SELECT OrderID FROM Orders;
OPEN cur; -- Starts implicit transaction
FETCH NEXT FROM cur INTO @id;
-- Now what? Transaction is open, cursor is mid-iteration.
-- If you COMMIT, cursor closes. If you don't COMMIT, it stays open forever.
CLOSE cur;
DEALLOCATE cur;
COMMIT;
This fragile interaction is why so much legacy cursor code is broken.
Problem 6: Deadlocks and Timeouts
Long-running transactions (because you forgot to commit) become deadlock victims. They also hit login timeouts:
-- Session 1: IMPLICIT_TRANSACTIONS ON
SELECT * FROM Orders WHERE Year = 2024; -- Starts transaction, holds lock
-- Developer forgets COMMIT for 5 minutes...
-- Session 2: Tries to SELECT the same table
SELECT * FROM Orders WHERE Year = 2023;
-- BLOCKED by Session 1
-- After 5 minutes:
-- Session 2 times out, errors out
-- Session 1 finally gets attention, COMMIT'ed
-- But by then, you've had a cascading failure
Real-World Example: The Overnight Outage
A production support team had IMPLICIT_TRANSACTIONS enabled in their batch process startup script:
-- batchproc_startup.sql
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET IMPLICIT_TRANSACTIONS ON; -- ← Someone added this in 2003 "for safety"
-- ... dozens of other settings ...
GO
For years, the batch ran fine. Then one night, a batch job crashed after UPDATE #1 but before COMMIT. The transaction hung open. Subsequent batch jobs tried to update the same tables. They blocked. The entire batch queue backed up within minutes. By morning, 8 hours of batch processing was stuck.
The DBA killed the hung session. Cascade of rollbacks. Jobs restarted. But the production database was down for 45 minutes.
Root cause? A forgotten COMMIT because IMPLICIT_TRANSACTIONS was ON.
Why Not Just Use Explicit Transactions?
If you want transaction control, use BEGIN TRANSACTION explicitly:
-- GOOD: Explicit, clear, intentional
BEGIN TRANSACTION;
INSERT INTO Orders (OrderID, CustomerID) VALUES (1001, 5);
INSERT INTO OrderDetails (OrderID, LineNo, Qty) VALUES (1001, 1, 10);
COMMIT; -- Explicit. Developer clearly intended this.
-- BAD: Implicit, unclear, dangerous
SET IMPLICIT_TRANSACTIONS ON;
INSERT INTO Orders (OrderID, CustomerID) VALUES (1001, 5);
INSERT INTO OrderDetails (OrderID, LineNo, Qty) VALUES (1001, 1, 10);
COMMIT; -- Developer might not remember why they have to commit
Explicit transactions make intent clear. Someone reading the code knows the developer wanted these two INSERTs to be atomic.
How to Disable It
In SSMS
SET IMPLICIT_TRANSACTIONS OFF;
Or go to Tools → Options → Query Execution → SQL Server → General and uncheck "Implicit Transactions."
In Application Code
-- Python (pyodbc)
conn.autocommit = True # Override server-side IMPLICIT_TRANSACTIONS
-- C# (ADO.NET)
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand("SET IMPLICIT_TRANSACTIONS OFF;", conn);
cmd.ExecuteNonQuery();
// Now proceed with autocommit
}
-- PowerShell (dbaTools)
Invoke-DbaQuery -SqlInstance server -Database db -Query "SET IMPLICIT_TRANSACTIONS OFF;"
At the Session Level (T-SQL)
-- Check current setting
SELECT @@OPTIONS & 2; -- Returns 2 if ON, 0 if OFF
-- Turn off
SET IMPLICIT_TRANSACTIONS OFF;
-- Verify
SELECT @@OPTIONS & 2; -- Should return 0
Finding IMPLICIT_TRANSACTIONS in Your Code
Search your codebase for the setting:
-- Find all stored procedures with IMPLICIT_TRANSACTIONS
SELECT
OBJECT_NAME(object_id) AS ProcName,
definition
FROM sys.sql_modules
WHERE definition LIKE '%IMPLICIT_TRANSACTIONS%';
-- Find startup scripts
-- Look in: SQL Server Agent jobs, login triggers, application connection strings
Best Practices
1. Disable IMPLICIT_TRANSACTIONS Globally
Add this to your startup scripts, login triggers, and connection initialization:
SET IMPLICIT_TRANSACTIONS OFF;
2. Use Explicit Transactions When Needed
BEGIN TRANSACTION;
UPDATE Inventory SET Qty = Qty - 10 WHERE ProductID = 5;
UPDATE SalesLog SET Units = Units + 10 WHERE ProductID = 5;
COMMIT;
-- Clear and intentional
3. Set XACT_ABORT ON (But Not IMPLICIT_TRANSACTIONS)
SET XACT_ABORT ON; -- ✓ Good: Rollbacks batch if error occurs
SET IMPLICIT_TRANSACTIONS OFF; -- ✓ Required: Keep this OFF
4. Use Try-Catch for Error Handling
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO Orders ...;
INSERT INTO OrderDetails ...;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
-- Handle error
END CATCH;
5. Always Commit or Rollback Explicitly
-- In a transaction, always end with explicit COMMIT or ROLLBACK
-- Never rely on implicit auto-commit
The Verdict
IMPLICIT_TRANSACTIONS is a dangerous legacy setting that adds no value and creates countless bugs. Turn it OFF everywhere — databases, sessions, applications, startup scripts. Use explicit BEGIN TRANSACTION when you need transaction control.
If you're maintaining code with IMPLICIT_TRANSACTIONS, refactor it to use explicit transactions. It's worth the effort. The debugging time you'll save will pay for it.
And if you find IMPLICIT_TRANSACTIONS enabled in your environment, disable it immediately. You won't regret it.