- Error number
- 601
- Severity
- 12 (user-correctable error)
- State
- Not documented by Microsoft (not reproduced here)
- Product
- Microsoft SQL Server Database Engine
- Versions
- All supported SQL Server versions (2016 to 2025), Azure SQL Database, Azure SQL Managed Instance
- Category
- Corruption & Consistency
- Last reviewed
- 24 October 2026
On this page
What it means
Error 601 means a query reading with NOLOCK or READ UNCOMMITTED lost its place because another transaction moved or removed the rows it was scanning, so SQL Server cancelled the query; it is a concurrency problem, not damage on disk.
Normally a reader waits for, or is protected from, rows that another transaction is changing. WITH (NOLOCK) and SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED remove that protection: the scan reads pages while other sessions split, move or deallocate them. If the page the scan needs next has gone away or now holds something else, the engine cannot continue the scan safely and cancels the query with 601.
The word "NOLOCK" in the message is the important part: it tells you the query chose dirty reads. Severity 12 is the level reserved for queries that do not lock because of query hints, and the data on disk is fine. The same mechanism can also produce transient error 605 at severity 12. Retrying usually works, but the same dirty reads that cause 601 can also silently return rows twice, skip rows or return uncommitted values, so the real fix is to stop reading uncommitted data.
Common causes
- A report or application query uses
WITH (NOLOCK)on a busy table while other sessions insert, delete or update rows. - A connection or procedure sets
TRANSACTION ISOLATION LEVEL READ UNCOMMITTED, so every table it reads behaves as if NOLOCK were used. - Index rebuilds, page splits from inserts into the middle of an index, or large deletes that move or deallocate pages during the scan.
- Long scans of large tables, which give concurrent changes more time to move the pages ahead of the scan.
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.
Which sessions read uncommitted data right now?
SELECT s.session_id, s.login_name, s.program_name, s.host_name,
s.transaction_isolation_level
FROM sys.dm_exec_sessions AS s
WHERE s.is_user_process = 1
AND s.transaction_isolation_level = 1; -- 1 = READ UNCOMMITTED Applications that run whole sessions in READ UNCOMMITTED. NOLOCK hints inside queries do not show here; see the next check.
Which procedures, views and functions use NOLOCK?
SELECT OBJECT_SCHEMA_NAME(m.object_id) AS schema_name,
OBJECT_NAME(m.object_id) AS object_name, o.type_desc
FROM sys.sql_modules AS m
JOIN sys.objects AS o ON o.object_id = m.object_id
WHERE m.definition LIKE N'%NOLOCK%'
OR m.definition LIKE N'%READUNCOMMITTED%'
OR m.definition LIKE N'%READ UNCOMMITTED%'; The modules to change. Match them to the query that failed (the application log usually records its text).
Which cached queries use NOLOCK?
SELECT TOP (20) qs.execution_count, qs.last_execution_time,
SUBSTRING(t.text, 1, 400) AS query_text
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS t
WHERE t.text LIKE N'%NOLOCK%'
ORDER BY qs.execution_count DESC; Ad hoc and application queries in the plan cache that use the hint, most frequently run first.
Is row versioning already available in the database?
SELECT name, is_read_committed_snapshot_on, snapshot_isolation_state_desc
FROM sys.databases
WHERE database_id = DB_ID(); If is_read_committed_snapshot_on = 1 (the default in Azure SQL Database), ordinary READ COMMITTED reads already avoid blocking and NOLOCK gains nothing. ON for snapshot isolation means sessions can use SNAPSHOT straight away.
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.
Retry the query once
Client or network sideMicrosoft notes that the query can simply be resubmitted; the data is not damaged. Use this as an immediate workaround only, because the dirty reads that caused 601 are still there.
Read committed data with snapshot isolation instead of NOLOCK
Changes configurationSnapshot isolation gives the reader a consistent view of committed data from row versions, without taking shared locks and without being blocked by writers. Allow it in the database, then switch the reporting session to it and remove the NOLOCK hints from its queries.
Caution: Row versioning stores versions in tempdb (or in the database with accelerated database recovery) and adds a few bytes to changed rows. Check tempdb space and test under load before enabling it in production.
ALTER DATABASE AppDb SET ALLOW_SNAPSHOT_ISOLATION ON;
-- in the reporting session or procedure
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
SELECT CustomerId, SUM(Amount) AS Total
FROM dbo.Orders -- no NOLOCK hint
GROUP BY CustomerId;
Or make READ COMMITTED use row versions for every query
Changes configurationREAD_COMMITTED_SNAPSHOT changes the default isolation level for the whole database to read committed row versions, which removes most of the blocking that NOLOCK was added to avoid, without changing queries. After that, remove the NOLOCK hints.
Caution: Turning on READ_COMMITTED_SNAPSHOT needs a moment with no other active connections in the database (WITH ROLLBACK IMMEDIATE rolls back their open transactions), and code that relies on readers being blocked can behave differently. Test first and do it in a maintenance window.
ALTER DATABASE AppDb SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
Remove NOLOCK and READ UNCOMMITTED from the code
Changes schemaAlter the procedures and views found by the diagnostics, and the application queries, so they use the default READ COMMITTED (or SNAPSHOT). If blocking is what NOLOCK was hiding, fix the blocking itself: shorter write transactions and indexes that let readers touch fewer rows.
Examples
A NOLOCK report scan while orders are being purged
Shown as documented by Microsoft; not reproduced here (a two-session attempt on SQL Server 2025 completed hundreds of NOLOCK scans without 601). The level and state a client sees were not observed, so only the message text is shown.
-- Session 1 (report)
SELECT COUNT(*) FROM dbo.Orders WITH (NOLOCK);
-- Session 2, at the same time (purge job)
DELETE TOP (50000) FROM dbo.Orders WHERE OrderDate < '20200101';
Could not continue scan with NOLOCK due to data movement.
How to prevent it
Do not use NOLOCK or READ UNCOMMITTED as a general speed-up. Where readers and writers block each other, use READ_COMMITTED_SNAPSHOT or snapshot isolation, which give consistent results without blocking, and keep write transactions short.
Add code review checks for NOLOCK hints and isolation level changes, and run large purges and index maintenance at times when long reports are not scanning the same tables.
Common questions
Is error 601 a sign of corruption?
No. It is raised because a dirty-read scan lost its position, and the data on disk is intact. If the same query also fails without NOLOCK, or DBCC CHECKDB reports errors, investigate corruption separately.
If I add retry logic, can I keep NOLOCK?
You can, but the retry only hides 601. NOLOCK can still return rows twice, miss rows and return values that are later rolled back, without any error. Snapshot isolation avoids both problems.
Does READ_COMMITTED_SNAPSHOT make NOLOCK unnecessary?
Yes for blocking: readers no longer wait for writers. A NOLOCK hint still overrides it and reads uncommitted data, so remove the hints as well.
Learn more on CodeWithSQL
Official documentation
- MSSQLSERVER_601 (Microsoft Learn)
- SET TRANSACTION ISOLATION LEVEL (Transact-SQL)
- Transaction locking and row versioning guide
- Table hints (Transact-SQL)
Last technically reviewed 24 October 2026; examples and diagnostics run on SQL Server 2025 (17.0.1145.4). Message text checked against the engine's sys.messages catalogue.