skip to content

A team wants to run a large reporting query with reads of uncommitted data so it stops waiting behind write transactions. How do you evaluate that request, and what would you propose instead?

level: seniorimportance: nice to knowfreq 28%

answer

  1. Ask what consumes the number, not how much faster
  2. Rollback → value never existed; unbounded error
  3. Lock-free scans can miss or double-count rows
  4. MVCC readers already don't block — check the premise
  5. Replica / snapshot read / pre-aggregate instead

basics

~20 s

Ask what consumes the result. Uncommitted reads are tolerable only for rough, human-eyeballed numbers that are never stored, compared to a threshold, or sent downstream. Otherwise propose a multi-version snapshot read, a read replica, or a precomputed aggregate instead of weakening correctness.

solid answer

~50 s

First I establish what the number is **for**. Uncommitted reads can return values that never existed after a rollback, and lock-free scans on some lock-based engines can additionally skip or double-count rows when pages move mid-scan — so the error is not bounded and not just 'slightly stale'. That makes the test simple: is the output written back, compared against a threshold, reconciled, invoiced, or shown as authoritative? If yes, uncommitted reads are disqualified regardless of the performance win. Then I attack the real problem, which is that readers block behind writers: - If the engine is multi-version, they **already** do not block — the hint is cargo cult; find the actual wait. - Route reporting to a **read replica** or a dedicated analytics copy. - Take a **consistent snapshot read** (a read-only transaction at snapshot isolation) so the report is internally consistent. - Precompute the aggregate incrementally so the report reads one small table. - Shorten the write transactions holding the locks.

go deeper

for a junior

Say that reading uncommitted data can return values that get rolled back, so it is only acceptable for rough numbers nobody stores, and suggest asking a senior before using it.

for a middle

Add the concrete hazards — rollback, partially applied multi-row transactions — and propose a read replica or a read-only snapshot transaction as the alternative.

for a senior

Lead with the consumer test, challenge the premise on a multi-version engine, name the scan-anomaly risk, and offer an ordered list of fixes ending with shortening the write transactions that actually hold the locks.

for a principal

Set it as policy: reporting does not run against the transactional hot path, staleness is an accepted and measured risk while uncommitted visibility is not, and any exception needs a named consumer and a written justification.

## Start with the consumer, not the level The request is framed as a performance problem, but the decision is a correctness one. So the first question is never 'how much faster is it?' — it is **what happens to this number afterwards?** A workable rule: - **Acceptable**: a human glances at an approximate progress count, a queue-depth gauge, a rough row estimate during a migration. Nobody stores it, nobody compares it to a limit, nothing downstream consumes it. - **Not acceptable**: anything written back to the database, compared against a threshold that triggers an action, reconciled against another system, invoiced, exported, or presented as authoritative to a user or auditor. The reason for the hard line is that the error from an uncommitted read is unbounded rather than proportional. A rolled-back writer means the value corresponds to no committed state at all; and because a transaction's writes become visible before commit, the reader can also observe a partially applied multi-row change — a debit without its credit — so a total can be wrong by the size of an entire in-flight transaction, not by a small drift. There is a second, less-known hazard specific to lock-free scans on engines that move rows during page splits or reorganizations: a scan holding no locks can traverse a structure that is being modified underneath it, and end up **missing committed rows or returning them twice**. That is worth naming, because it defeats the usual defence of 'the count is only a bit off — it will be roughly right'. ## Then check whether the premise even holds Often the request is inherited folklore. On a multi-version engine, readers do not block behind writers in the first place: they read older committed versions rather than waiting. If someone is adding an uncommitted-read hint there, it is doing nothing, and the actual wait is somewhere else — a lock taken explicitly, a schema-change operation, a resource bottleneck, or plain query cost. Measure the wait before changing semantics. ## The alternatives, in the order I would propose them 1. **Fix the query.** A reporting query that scans far more than it needs holds resources longer and conflicts with more writers. Indexing, narrowing the range, or pre-aggregating often removes the problem entirely. 2. **Read-only, snapshot-consistent transaction.** Declaring the transaction read-only and running it against a consistent snapshot gives a report that is internally consistent — every table agreeing with every other — which is *better* than the status quo, not worse. This is usually the direct answer to 'we want to stop blocking'. 3. **A read replica or an analytics copy.** Moves the load off the transactional path completely. Costs replication lag, which is honest, bounded staleness of committed data — a categorically safer error than uncommitted data. 4. **Incremental pre-aggregation.** Maintain the total as writes happen (a summary table, materialized aggregate, or event-driven rollup) so the report reads one small, already-consistent row set. 5. **Shorten the write transactions.** If reports are blocking behind writers on a lock-based engine, the writers holding locks for a long time are the real defect. No user waits, no external calls, no batch loops inside a transaction. 6. **Bound the report's blast radius.** Timeouts, a lower resource priority, and a schedule outside peak write windows. ## How I would close the conversation I would agree to uncommitted reads only with three things written down: the specific query, a statement that its output is display-only and never persisted or thresholded, and a comment at the call site explaining why. Anything else, and I would rather spend the same effort on a replica or a pre-aggregate, because those fix the class of problem instead of one query. The underlying principle worth saying out loud in an interview: **staleness and incorrectness are not the same risk**. Replica lag and cached aggregates give you data that was true at some point; uncommitted reads give you data that may never have been true. Almost every reporting requirement tolerates the former and none should tolerate the latter without an explicit, narrow justification.

  • The team argues the report only needs to be approximately right, so an occasional wrong value is fine. What is the flaw?
    The error from an uncommitted read is not proportional to how approximate you allow the answer to be. A rolled-back writer makes a value that never existed, a partially applied multi-row transaction can shift a total by that transaction's entire size, and a lock-free scan can miss or double-count committed rows outright. 'Approximately right' assumes bounded drift, which this mechanism does not provide.
  • Why is replication lag a safer form of wrongness than an uncommitted read?
    A lagging replica returns data that was genuinely committed at some earlier instant, so the result corresponds to a real, consistent state of the database and the error is bounded by the measurable lag. An uncommitted read can return a value that no committed state ever contained, and can mix committed and never-committed data in one result. One is stale truth; the other is fiction.

saying these in an interview costs you the question

  • Treating uncommitted reads as merely 'slightly stale' data
  • Approving it for a number that is stored, thresholded, or sent to another system
  • Adding an uncommitted-read hint on a multi-version engine, where readers never blocked anyway
  • Not measuring what the reader was actually waiting on before changing isolation semantics
  • Ignoring that lock-free scans can miss or double-count committed rows, not just read uncommitted ones

context