Skip to content
SeriouslySQL
Go back
Deep Dive

NOLOCK Isn't Free

WITH (NOLOCK) tells SQL Server not to take shared locks when reading. No locks means no waiting on other transactions, which is why queries feel faster with it. What it also means is you can read uncommitted data: rows mid-transaction, rows about to be rolled back. You don’t know which you’re getting.

That’s a dirty read. NOLOCK trades read consistency for speed. Whether that’s acceptable depends on what you’re doing with the results.

What people think it does

The common belief is that NOLOCK means “read-only, don’t bother SQL Server with locks.” That’s not what it does. A SELECT without NOLOCK already doesn’t block other SELECTs. Shared locks don’t fight each other. The problem NOLOCK solves is a SELECT blocking on an exclusive lock held by a write, or a write waiting for a shared lock held by a long-running read.

If your query is slow because it’s doing a lot of work, NOLOCK won’t help. If it’s slow because it’s waiting on a lock, NOLOCK will help, but so will fixing whatever is holding the lock.

The actual risks

Dirty reads are the obvious one. Less obvious:

Phantom reads. A row can appear twice, or not at all if a page split happens while SQL Server is scanning. The scan follows the page chain, and if that chain shifts mid-scan, you can get duplicates or miss rows entirely.

Non-repeatable reads. Read the same row twice in the same query, get different values each time, because another transaction changed it between reads.

Errors. SQL Server can raise error 601 (“Could not continue scan with NOLOCK due to data movement”) in some cases. The query fails rather than returning bad data.

None of this shows up in normal testing because you’d need concurrent writes at exactly the wrong moment. It shows up in production.

When it might be worth it

Reporting queries against large tables, where you’re aggregating millions of rows and you know the data’s not going to be perfect anyway. Log-style tables where you’re just trying to get a rough count. Anything where “close enough” is genuinely good enough.

The key word is genuinely. “It’s only a report” is fine if the report drives decisions that can tolerate a few dodgy rows. It’s not fine if someone’s acting on exact figures.

What to use instead

Read Committed Snapshot Isolation (RCSI) is what most people should be using. It gives you non-blocking reads without dirty reads. Readers don’t block writers, writers don’t block readers, and you still get committed data. The cost is version store overhead in tempdb, but on modern SQL Server that’s usually worth it.

To check if it’s enabled:

SELECT name, is_read_committed_snapshot_on
FROM sys.databases
WHERE name = DB_NAME();SQL

To enable it (needs brief exclusive access to the database):

ALTER DATABASE YourDatabase
SET READ_COMMITTED_SNAPSHOT ON;SQL

If RCSI isn’t an option and you’re stuck with NOLOCK for a specific query, at least be deliberate about it. Know what data you’re reading, know why dirty reads are acceptable in that context, and document it. Don’t just leave WITH (NOLOCK) scattered through the codebase because it made things faster once.

Seriously.


Share this post:

Previous Post
Where have all the graduate IT roles gone?
Next Post
Your SQL Server Version Isn't the Whole Story