Skip to content

CONCEPT Cited by 1 source

Dirty read

A dirty read is a read-anomaly class that occurs when one transaction observes another transaction's uncommitted writes. If the writing transaction later rolls back, the reading transaction has observed data that "never officially existed" in the database's committed history — the read was based on a transient in-progress state that was then erased.

Canonical worked example

From PlanetScale's Brian Morrison II (Source: sources/2026-04-21-planetscale-mysql-isolation-levels-and-how-they-work):

Two customers look at a CD store's inventory; each wants to buy 2 copies of the same CD. Customer A starts a transaction that moves 2 units from available to sold_not_shipped:

START TRANSACTION;
UPDATE unit_status SET quantity = quantity + 2
  WHERE product_id = 20 AND status = 'sold_not_shipped';
UPDATE unit_status SET quantity = quantity - 2
  WHERE product_id = 20 AND status = 'available';
COMMIT;  -- (but suppose it rolls back instead)

Customer B, running concurrently under READ UNCOMMITTED, queries:

SELECT quantity FROM unit_status
  WHERE product_id = 20 AND status = 'available';

If this SELECT runs after Customer A's second UPDATE but before Customer A's COMMIT, it observes quantity = 0 — even though those 2 units haven't actually been sold. If Customer A then issues ROLLBACK, the inventory reverts to what it was, and Customer B has read a value that never committed to the database.

Which isolation levels permit it

Level Dirty read
READ UNCOMMITTED allowed
READ COMMITTED prevented
REPEATABLE READ prevented
SERIALIZABLE prevented

Dirty reads are the characteristic violation permitted only by READ UNCOMMITTED and prevented by every higher level — which is why READ COMMITTED is the lowest level most applications would use in production. See MySQL transaction isolation levels.

Mechanism in InnoDB

Under READ UNCOMMITTED, InnoDB reads the latest version of a row regardless of which transaction wrote it and regardless of whether that transaction has committed. Under READ COMMITTED and above, reads only observe row versions tagged by transactions that have committed — uncommitted writes are invisible by design.

The MVCC substrate ( Multi-Version Concurrency Control) maintains multiple row versions per table cell, keyed by the writing transaction's ID. The isolation level determines which version a read sees:

  • READ UNCOMMITTED: newest version, committed or not.
  • READ COMMITTED: newest committed version at the moment of the SELECT.
  • REPEATABLE READ: newest committed version as of the transaction's start.

When READ UNCOMMITTED is the right choice

Brian Morrison II gives the canonical use case: "This mode should be used when performance takes priority over data consistency." Example: a social-media app rendering the like count on a viral post — "Many social media apps shorten their numbers after a certain point (ie; 1k over 1,000). If a client needs to know how many likes the post has, returning an approximation over the exact value is usually acceptable."

The justification is that the error bound from dirty reads is typically small on high-throughput aggregates (a few uncommitted values per million committed ones) and the latency / concurrency gain from not taking locks or snapshotting is larger than the correctness cost.

This is rarely the right choice for:

  • Financial data, inventory counts, or any value users will be told is "authoritative".
  • Any compound check-then-act logic (e.g., "is stock available? → decrement") — dirty reads make the check unreliable.
  • Any value that will be copied elsewhere (e.g., into a report, another database, an email) where the reader can't distinguish uncommitted-then-rolled-back data from committed data.
  • Non-repeatable read — same transaction's two reads of the same row return different committed values. Dirty read reads uncommitted; non-repeatable reads both reads are of committed data but from different points in time.
  • Phantom read — same transaction's two reads with the same predicate return different row sets because another transaction inserted matching rows. Phantom reads are about row existence, not value change on existing rows.
  • Snapshot isolation — an isolation model (Postgres-flavoured REPEATABLE READ, sometimes called SI) that prevents dirty reads + non- repeatable reads + phantom reads but allows write-skew. In MySQL, this is roughly what REPEATABLE READ offers on locking reads.

Seen in

  • sources/2026-04-21-planetscale-mysql-isolation-levels-and-how-they-work — Brian Morrison II's PlanetScale pedagogical post canonicalises dirty reads as the first of the three violations, with the CD-store inventory worked example and the social-media like-counting use case for when READ UNCOMMITTED is tolerable. The verbatim framing: "A dirty read occurs when a query within one transaction returns inconsistent data because it may read a new version of the data that has not yet been committed to the database based on another concurrent transaction."
Last updated · 378 distilled / 1,213 read