SQL Server Isolation Levels: READ UNCOMMITTED to Snapshot (2026)

SQL Server Isolation Levels

Isolation levels define the trade-off between data consistency and concurrency. Stronger isolation prevents more anomalies but requires more locking, producing more blocking. Weaker isolation allows anomalies but reduces contention. SQL Server offers six isolation levels — four traditional lock-based levels and two row-versioning levels that sidestep the lock conflict problem entirely.

Concurrency Anomalies: Dirty, Non-Repeatable, Phantom Reads

Three classic read anomalies define what isolation levels must prevent:

Dirty Read: Session A reads a row that Session B has modified but not yet committed. If B rolls back, A read data that never existed.

-- Session B
BEGIN TRAN;
UPDATE dbo.Accounts SET Balance = 999999 WHERE ID = 1;
-- (not yet committed)

-- Session A (READ UNCOMMITTED)
SELECT Balance FROM dbo.Accounts WHERE ID = 1;  -- sees 999999 (uncommitted!)

-- Session B
ROLLBACK;  -- 999999 never actually existed

Non-Repeatable Read: Session A reads a row, Session B updates and commits it, Session A reads it again in the same transaction and gets a different value.

Phantom Read: Session A queries a range (e.g., WHERE Amount > 1000), Session B inserts a new row in that range and commits, Session A re-queries the range and sees the new "phantom" row.

Isolation Level Summary Table

Isolation LevelDirty ReadNon-RepeatablePhantomLockingTempDB
READ UNCOMMITTEDYesYesYesNo S locksNo
READ COMMITTEDNoYesYesS locks released after readNo
REPEATABLE READNoNoYesS locks held to end of txnNo
SERIALIZABLENoNoNoRange locksNo
RCSINoYes*Yes*No S locks (versions)Yes
Snapshot IsolationNoNoNoNo S locks (versions)Yes

* RCSI reads the latest committed version, not a point-in-time snapshot, so non-repeatable reads are possible within the same transaction.

READ UNCOMMITTED (NOLOCK)

The weakest isolation level. Readers acquire no shared locks and ignore exclusive locks held by writers. This means reads never block writes and writes never block reads — but at the cost of dirty reads.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Or per table:
SELECT * FROM dbo.Orders WITH (NOLOCK);
NOLOCK risks beyond dirty reads:
  • A row can be read twice if a page split occurs mid-scan and the scanner revisits the moved rows
  • A row can be missed entirely for the same reason
  • NULL values can appear in NOT NULL columns from in-flight row updates
  • Referential integrity violations can appear (a child row visible before parent is committed)
Use RCSI instead — it gives consistent reads with no blocking and none of these hazards.

READ COMMITTED (Default)

The SQL Server default. Shared locks are acquired when reading and released immediately after the row is read (not held until end of transaction). This prevents dirty reads — a row cannot be read while another session holds an X lock on it.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;  -- default; usually not needed explicitly

The "released immediately" behaviour means non-repeatable reads are possible: a value read at the start of a long transaction may be different if re-read later in the same transaction. For most OLTP workloads this is acceptable.

READ COMMITTED under RCSI: If READ_COMMITTED_SNAPSHOT is ON for the database, READ COMMITTED automatically becomes RCSI-based — it reads from row versions instead of taking shared locks. No application code change needed.

REPEATABLE READ

Shared locks are held until the end of the transaction, preventing other sessions from modifying any row you have read. This eliminates non-repeatable reads — if you re-read a row in the same transaction, it will have the same value.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN;
SELECT Balance FROM dbo.Accounts WHERE ID = 1;  -- S lock held
-- ... other work ...
SELECT Balance FROM dbo.Accounts WHERE ID = 1;  -- guaranteed same value
COMMIT;

Phantom reads are still possible — another session can INSERT new rows matching your WHERE clause. Only SERIALIZABLE prevents phantoms.

Blocking impact: Holding S locks for the transaction duration means writers are blocked for the entire life of the transaction. On long-running reporting transactions this can cascade into severe blocking chains.

SERIALIZABLE

The strongest lock-based isolation level. In addition to holding S locks to end of transaction, SQL Server also acquires key-range locks that prevent new rows being inserted into any range you have queried. This prevents phantom reads.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN;
SELECT * FROM dbo.Orders WHERE OrderDate = '2026-06-11';
-- Key-range locks on the 2026-06-11 gap; no other session can insert for this date
COMMIT;

Serializable means all transactions appear to execute in serial order — the gold standard for correctness. But it produces the most blocking and the most deadlocks. Use it only when:

  • You genuinely need to prevent phantom reads (financial summaries, audit trails)
  • The transaction is short and the locked range is narrow
  • You cannot use Snapshot Isolation for this workload

Read Committed Snapshot Isolation (RCSI)

RCSI is a database-level setting that changes the behaviour of the READ COMMITTED isolation level. Instead of taking S locks, readers read the last committed version of each row from TempDB's version store.

What RCSI provides:

  • No dirty reads (versions are committed values only)
  • No reader-writer blocking
  • Statement-level consistency (each statement sees a snapshot of committed data at the moment the statement started)

What RCSI does NOT provide:

  • Transaction-level consistency — within the same transaction, two SELECT statements may see different data if another transaction committed between them
-- Enable RCSI
ALTER DATABASE YourDB SET READ_COMMITTED_SNAPSHOT ON;

-- All READ COMMITTED sessions now use row versions automatically
-- No application changes needed

Snapshot Isolation (SI)

Snapshot Isolation is a session-level isolation level (unlike RCSI which is database-wide). It gives each transaction a transaction-consistent snapshot of the database as it existed when the transaction started. All reads within the transaction see the same version of data regardless of when in the transaction they execute.

-- Enable at database level (opt-in for individual sessions)
ALTER DATABASE YourDB SET ALLOW_SNAPSHOT_ISOLATION ON;

-- Use in a session
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRAN;
SELECT Balance FROM dbo.Accounts WHERE ID = 1;  -- snapshot of transaction start time
-- ... 30 seconds later ...
SELECT Balance FROM dbo.Accounts WHERE ID = 1;  -- still same value (transaction-consistent)
COMMIT;

SI prevents dirty reads, non-repeatable reads, and phantom reads — matching Serializable consistency — but without range locks. Writers still take X locks and writers block writers, but readers never block.

Update conflict detection: Under SI, if two transactions both read the same row and both try to update it, the second UPDATE gets a snapshot isolation update conflict error (error 3960). This does not happen under RCSI. Applications using SI for write transactions must handle this error.

RCSI vs SI: Key Differences

RCSISnapshot Isolation
ScopeDatabase-wide (replaces READ COMMITTED)Per-session opt-in
Snapshot pointStatement startTransaction start
Non-repeatable readsPossible (different statements see different data)Not possible (consistent throughout transaction)
Update conflictsNoYes (error 3960)
Version store usageVersions only needed for duration of active statementsVersions needed for entire transaction duration
Best forOLTP — eliminates reader-writer blocking with no app changesLong-running analytics needing a consistent snapshot

Setting Isolation Level

-- Session level
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;  -- requires ALLOW_SNAPSHOT_ISOLATION ON

-- Query level (hint overrides session setting for that query)
SELECT * FROM dbo.Orders WITH (READUNCOMMITTED);  -- = READ UNCOMMITTED
SELECT * FROM dbo.Orders WITH (READCOMMITTED);
SELECT * FROM dbo.Orders WITH (REPEATABLEREAD);
SELECT * FROM dbo.Orders WITH (SERIALIZABLE);
SELECT * FROM dbo.Orders WITH (SNAPSHOT);

-- Check current session isolation level
SELECT transaction_isolation_level,
       CASE transaction_isolation_level
         WHEN 0 THEN 'Unspecified'
         WHEN 1 THEN 'ReadUncommitted'
         WHEN 2 THEN 'ReadCommitted'
         WHEN 3 THEN 'RepeatableRead'
         WHEN 4 THEN 'Serializable'
         WHEN 5 THEN 'Snapshot'
       END AS isolation_name
FROM sys.dm_exec_sessions
WHERE session_id = @@SPID;

Decision Guide: Which Isolation Level to Use

ScenarioRecommended Level
Standard OLTP (web/app server)READ COMMITTED + RCSI enabled
Analytics query needing consistent snapshotSnapshot Isolation
Financial batch — must prevent any concurrent changeSERIALIZABLE (short transactions only)
Approximate reporting, dirty reads acceptableREAD UNCOMMITTED (or use RCSI instead)
Re-read same rows multiple times in transactionREPEATABLE READ or Snapshot Isolation
Bulk load staging tableREAD COMMITTED (or BULK INSERT with TABLOCK)

Read Next

← SQL Server Hub