Phantom Reads: When New Rows Appear Mid-Transaction

A phantom read occurs when a transaction repeats a query and finds new rows that match its search criteria, inserted by another committed transaction. It's common in reporting jobs that need a stable set of data.
Why it exists
When multiple transactions run concurrently, one transaction might add new data that affects another's view of the database. A phantom read occurs when a transaction sees an inconsistent set of rows, which can lead to incorrect calculations or logical errors in multi-step operations that expect a stable dataset.
The mental model
Imagine you are counting all the blue cars on a street for a report. You walk the block and count 10. While you're walking back to your office, someone parks a new blue car. To double-check your numbers, you count again and find 11. That 11th car is a 'phantom'. Your query for 'blue cars' returned a different set of results within the same overall task. This is distinct from a non-repeatable read, which would be if one of the original 10 cars was repainted from blue to red.
How it works
A phantom read happens within a single transaction when it executes the same range query (a query with a WHERE clause) twice. Between these two executions, another transaction successfully commits an INSERT or DELETE that affects which rows match the first transaction's WHERE clause. The second time the query runs, it sees these 'phantom' rows that weren't there before, or it fails to see rows that have been deleted.
When to use it
You don't 'use' a phantom read; you prevent it. You must account for phantom reads in any transaction that requires a consistent view of a set of rows. This is critical for multi-step reporting jobs, financial calculations that sum up accounts matching certain criteria, or inventory checks that count items in a specific category. If the set of items cannot change mid-operation, you need an isolation level like Repeatable Read or Serializable.
When not to use it
For transactions where the exact set of rows doesn't need to be perfectly stable, a lower isolation level like Read Committed is often sufficient and more performant. If you are only reading a single row by its primary key, or if your application logic can tolerate a changing set of results between queries, the overhead of preventing phantoms may not be justified.
One canonical example
Transaction A starts at the 'Read Committed' isolation level.
First, Transaction A runs SELECT COUNT(*) FROM employees WHERE salary > 100000; and gets the result 15.
Then, Transaction B inserts a new highly-paid employee and commits: INSERT INTO employees (name, salary) VALUES ('Charlie', 120000); COMMIT;
Finally, Transaction A, still running, executes its query again for a final report: SELECT COUNT(*) FROM employees WHERE salary > 100000;. This time, it gets the result 16. The new employee, Charlie, is the phantom row that appeared mid-transaction.
Interview question
In which scenario would a phantom read most likely lead to incorrect application logic?
- a.A financial transaction debits an account, but before it commits, another transaction tries to read the account balance, seeing the temporary debited amount.
- b.A reporting job counts all active accounts, and then later in the same transaction, recounts them, finding a different total due to new accounts being added by another committed transaction.Correct
- c.A transaction reads a customer's order details, and then later in the same transaction, re-reads the same order details, finding that the shipping address has been updated by another committed transaction.
- d.A user updates their profile information, and another user immediately views the profile, seeing the old data.
Why? this is the answer
A phantom read occurs when a transaction re-executes a range query and finds new or missing rows due to another committed transaction, causing inconsistent results for operations like reporting or financial calculations. Option B directly illustrates this problem for a reporting job. Option C describes a non-repeatable read, where an existing row's data changes, not the set of rows returned by the query.
Just read this? Test yourself on what you have been reading.
Read the original → postgresql.org
- #databases
- #concurrency
- #acid
- #transactions
- #isolation
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on databases — each one lists the topics its interview covers.
See open roles