Hook: Interviewers love this because it checks whether you understand that a transaction can be reading a moving target, not a frozen picture.
Question: What is a non-repeatable read in SQL?
Answer: A non-repeatable read happens when I read the same row twice inside one transaction and get different values the second time because another transaction committed an update or delete in between. It usually appears at weaker isolation levels such as READ COMMITTED, where each statement can see a fresh view of the data. The fix is usually stronger isolation like REPEATABLE READ or SERIALIZABLE, or locking the row when I truly need it to stay stable.
Interview-Ready Answer: A non-repeatable read is when I query the same row twice in one transaction and the result changes because another transaction committed an update or delete in between. In practice, this is allowed by READ COMMITTED, where each statement can see a new snapshot. If I need the value to stay stable, I use REPEATABLE READ or SERIALIZABLE, or I take a lock so no one can change that row mid-transaction.
100.80.80, not 100. That is a non-repeatable read: the same read is not repeatable inside one transaction.SELECT and the second SELECT may see different committed versions.SELECT ... FOR UPDATE, the database can block conflicting writers so the row does not change while you depend on it.They want to know whether you can explain inconsistency bugs that only happen under concurrency. A lot of real production issues are not about bad SQL syntax; they are about two valid transactions stepping on each other.
| Anomaly | What changes? | Simple example |
|---|---|---|
| Dirty read | Uncommitted data | Read a value that later rolls back |
| Non-repeatable read | Committed row value | Same row differs on second read |
| Phantom read | Matching rows set | Query returns extra rows later |
READ COMMITTED when each statement can safely stand alone and you care more about throughput.REPEATABLE READ when a business decision depends on a stable value, such as a balance, seat count, or stock level.SERIALIZABLE when correctness matters more than concurrency and you want the database to behave as if transactions ran one at a time.50-100 ms of extra waiting on one hot row can cascade into timeouts and retries.READ COMMITTED, MySQL InnoDB defaults to REPEATABLE READ, and SQL Server defaults to READ COMMITTED unless snapshot options are enabled.Memory in one line: If you need the same answer twice, take a photocopy at the start; otherwise you are reading a whiteboard someone can erase between glances.
Real-World Example: Imagine a checkout service that verifies a customer's wallet balance before placing an order. The service reads the balance at the start of the transaction, then reads it again after calculating tax and discounts.
150, so the order looks safe.120 or 180.The bug shows up as flaky behavior: support sees users getting inconsistent totals, logs show balance_before and balance_after disagreeing for the same request id, and retries make the problem look random. The fix is usually to keep the critical row stable with stronger isolation or a row lock while the decision is being made.
-- Non-repeatable read demo in PostgreSQL-style SQL.
-- Open TWO SQL sessions (Session A and Session B) against the same database.
-- This script creates a tiny table and shows how the same row can change
-- inside one transaction under READ COMMITTED.
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
balance INTEGER NOT NULL
);
INSERT INTO accounts (id, owner, balance)
VALUES (1, 'Ava', 100);
-- ---------------------------
-- Session A: start a transaction and read the row.
-- ---------------------------
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT balance AS first_read
FROM accounts
WHERE id = 1;
-- Leave Session A open here.
-- ---------------------------
-- Session B: make a committed change to the same row.
-- ---------------------------
-- In a second session, run:
-- BEGIN;
-- UPDATE accounts
-- SET balance = balance - 30
-- WHERE id = 1;
-- COMMIT;
-- ---------------------------
-- Back in Session A: read the same row again.
-- Under READ COMMITTED, this can see the new committed value.
-- ---------------------------
SELECT balance AS second_read
FROM accounts
WHERE id = 1;
COMMIT;
-- Expected idea:
-- first_read = 100
-- second_read = 70
-- That difference is the non-repeatable read.
-- ---------------------------
-- Safer pattern: lock the row when the value must not change.
-- This is useful for inventory, balances, or seat counts.
-- ---------------------------
BEGIN;
SELECT balance
FROM accounts
WHERE id = 1
FOR UPDATE;
-- While this transaction is open, another transaction trying to UPDATE
-- the same row will have to wait or fail depending on timeout settings.
-- That prevents the value from changing between checks.
COMMIT;
-- Edge case: if the row is deleted by another committed transaction,
-- a second read may return no row at all, which is still a non-repeatable read
-- because the result of the earlier read is no longer repeatable.Follow-up & Tricky Questions:
REPEATABLE READ and SERIALIZABLE do, while READ COMMITTED allows them.REPEATABLE READ block writers? Not always. In MVCC databases, readers often do not block writers, but the transaction keeps seeing the same snapshot.FOR UPDATE, or re-check the version before writing.READ COMMITTED always produce non-repeatable reads? No, it only allows them. You need the right timing and a concurrent committed update.Common Mistakes:
Memory Hook: One row, two looks, two different truths — if the page changes between peeks, you did not get a repeatable read.
Cheat Sheet:
READ COMMITTED.REPEATABLE READ, SERIALIZABLE, or row locks.Practice Tasks:
FOR UPDATE and explain why the concurrent update now waits.