Dirty read

On this page 7

A dirty read is a concurrency anomaly in which one transaction reads data written by another transaction that has not yet committed. If the writing transaction later rolls back, the reader has seen values that never officially existed and may already have acted on them. In the theory of transactions, the dirty read is the most basic violation of isolation, the I in ACID.

In Databases, the SQL standard defines four isolation levels by which anomalies they permit, and the dirty read is the anomaly that only the lowest level, READ UNCOMMITTED, allows. Most production systems never produce dirty reads by default, but they still appear through explicit settings, query hints and some reporting tools.

How a dirty read happens

A dirty read requires two overlapping transactions. Transaction A modifies a row but has not committed. Transaction B, running at an isolation level that ignores uncommitted changes, reads that row and sees the new, uncommitted value. If A then rolls back, the database restores the old value, but B has already used the discarded one.

The sequence below shows the classic case with an account balance of 1,000.

StepTransaction ATransaction BBalance seen
1Begins, sets balance to 400Not yet startedStored uncommitted: 400
2Still open, not committedReads balance at READ UNCOMMITTEDB sees 400
3Rolls back after a failed checkHolds the value 400Committed value: 1,000
4FinishedRejects a payment for lack of fundsDecision based on 400
Timeline of a dirty read

Whether the database can produce this depends on its concurrency control. Lock-based engines prevent dirty reads by making readers request shared locks, which wait for writers’ exclusive locks. Engines using multiversion concurrency control (MVCC) keep older committed row versions and show readers only committed data, so readers neither wait nor see uncommitted changes.

Dirty reads and the isolation levels

The SQL standard ranks isolation levels by which read phenomena they allow. Dirty reads are excluded from every level above READ UNCOMMITTED, while non-repeatable reads and phantom reads are excluded only at higher levels.

Isolation levelDirty readNon-repeatable readPhantom read
READ UNCOMMITTEDPossiblePossiblePossible
READ COMMITTEDPreventedPossiblePossible
REPEATABLE READPreventedPreventedPossible in the standard
SERIALIZABLEPreventedPreventedPrevented
Anomalies permitted by each standard isolation level

Real systems often provide stronger guarantees than the standard requires. PostgreSQL accepts the READ UNCOMMITTED setting but treats it as READ COMMITTED, so it never returns dirty data. Oracle Database does not offer READ UNCOMMITTED at all. SQL Server and MySQL with InnoDB do support it and will return uncommitted data at that level.

A dirty read is about seeing uncommitted data, whereas the neighboring anomalies involve committed data that changes between two reads in the same transaction. Telling them apart matters, because each requires a different isolation level or technique.

AnomalyWhat the reader seesLowest level that prevents it
Dirty readAnother transaction’s uncommitted changeREAD COMMITTED
Non-repeatable readA row changed by a committed transaction between two readsREPEATABLE READ
Phantom readNew or removed rows matching a repeated querySERIALIZABLE
Lost updateIts own write overwritten by a concurrent writeVaries; often needs locking or versioning
Write skewTwo valid writes that together break a ruleSERIALIZABLE
Dirty read compared with other anomalies

A related but distinct risk at READ UNCOMMITTED in lock-based engines is that scans can skip rows or read the same row twice when pages split or rows move during the scan. This means the results are not only possibly uncommitted but also internally inconsistent.

Where dirty reads appear in practice

Dirty reads usually appear when someone deliberately lowers isolation to avoid blocking. The most common example is the SQL Server table hint WITH (NOLOCK), which is equivalent to READ UNCOMMITTED for that table and is frequently added to reporting queries to stop them from waiting on writers.

The table summarizes typical sources and safer alternatives.

SourceWhy it is usedSafer alternative
`WITH (NOLOCK)` hintAvoids blocking on busy tablesRead committed snapshot isolation
Session set to READ UNCOMMITTEDFaster ad hoc analysisSnapshot isolation or a read replica
Reporting tool defaultsTool sets the lowest level by defaultConfigure READ COMMITTED explicitly
Monitoring queriesProgress of long-running jobsAcceptable when approximate data is fine
Sources of dirty reads and alternatives

There are legitimate uses: watching the progress of a large batch load, or computing rough row counts on a dashboard where exactness does not matter. The rule is that dirty data must never drive a business decision or be written back into the database.

Preventing dirty reads without heavy blocking

The usual motive for accepting dirty reads is reader-writer blocking, and modern engines solve that problem without sacrificing correctness. Row versioning lets readers see the last committed version while writers proceed.

  • Use READ COMMITTED as the minimum for any transaction whose results affect money, inventory or user-visible state.
  • Enable read committed snapshot isolation in SQL Server, which removes most reasons for NOLOCK.
  • Keep write transactions short, so locks and versions are released quickly.
  • Move heavy reporting to replicas or a data warehouse instead of lowering isolation on the primary.
  • Search code for NOLOCK hints during reviews and require a written justification for each one.

The simple explanation

A dirty read is like reading a draft that its author might still throw away. Someone is in the middle of changing a record. Another person looks at it and sees the half-finished change.

If the first person then cancels, the second person has acted on information that never became real. Databases prevent this by showing each reader only changes that have been fully saved.

How it is used in practice

An e-commerce company’s stock dashboard used queries with WITH (NOLOCK) to avoid slowing down order processing. During a busy sale, a large order transaction reduced the stock of a popular product to zero and then rolled back because the payment failed. The dashboard query had already read the zero, and an automatic rule marked the product as sold out for almost an hour.

The database team enabled read committed snapshot isolation on the database and removed the hints. Dashboard queries now read the last committed stock values without blocking order processing, the false sold-out events stopped, and no measurable slowdown appeared in checkout response times.