Two concurrent transactions running under snapshot isolation both read the same inventory row and both issue an update to it. Describe exactly what the database does, what error the loser sees, and which classic anomaly this rule does and does not prevent.
answer
- concurrent writes to same row: one dies
- first-committer-wins = check at commit
- first-updater-wins = block at UPDATE
- serialization failure is retryable
- kills lost update, not write skew
basics
~20 sOnly one can commit. Under first-committer-wins the second commit is rejected; under first-updater-wins the second update blocks until the first ends and then aborts. The loser gets a serialization failure and must retry. This prevents lost update, not write skew.
solid answer
~50 sSnapshot isolation forbids two concurrent transactions from writing the same row. Engines implement this in one of two ways. **First-committer-wins** lets both proceed and validates at commit: the later committer discovers the row was modified by a transaction not visible in its snapshot and aborts. **First-updater-wins** takes a row lock at UPDATE time, so the second transaction blocks; when the first commits, the second aborts immediately, and if the first rolls back, the second proceeds. Both surface as a serialization failure — a transient, retryable error — and both give the same outcome. This rule is what makes SI immune to **lost update**: the read-modify-write pattern where both transactions read 10 and both write 11 cannot both commit. It is *not* protection against **write skew**, because there the write sets are disjoint and no conflict exists to detect. The application obligation is a retry that restarts the whole transaction so the re-read sees the winner's value.
code
sql · 6 lines-- vulnerable shape: value decided outside the engine
SELECT stock FROM item WHERE id = 7; -- reads 10 from the snapshot
UPDATE item SET stock = 9 WHERE id = 7;
-- conflict-free shape: engine re-reads the current row under lock
UPDATE item SET stock = stock - 1 WHERE id = 7 AND stock > 0;go deeper
Know that two concurrent transactions cannot both update the same row under snapshot isolation and that one gets an error it must retry.
Distinguish first-committer-wins from first-updater-wins, name the error class as a retryable serialization failure, and state that this closes lost update but not write skew.
Add the operational view: hot-row contention turns the rule into a throughput ceiling, abort rate scales with transaction length times contention, and atomic in-place updates or sharded counters are the structural fixes.
Decide where this contention is allowed to exist at all: which counters and aggregates may live on a hot row, which move to sharded or asynchronous designs, and what abort rate the platform will treat as a design defect rather than absorb with retries.
## The rule Snapshot isolation is defined by two halves. The read half: a transaction sees the database as of its start. The write half: **two concurrent transactions may not both modify the same data item**. "Concurrent" means their execution intervals overlap — specifically, the other transaction committed after this transaction's snapshot was taken. Without the write half, both transactions would read from their own snapshots and blindly overwrite each other, and the update that landed first would vanish with no trace. ## Two implementations of the same rule **First-committer-wins** is the abstract formulation. Both transactions update their own private versions of the row. At commit time the engine checks whether any *other* transaction committed a write to the same row since this transaction's snapshot was taken. If so, this transaction aborts. Whoever commits first is the winner purely by timing; the second one's work is discarded. **First-updater-wins** is what most MVCC engines actually do, because it avoids doing work that will be thrown away. When a transaction issues the UPDATE it takes an exclusive row lock. If another live transaction already holds it, the second transaction *blocks* — this is the one place snapshot isolation makes a writer wait. When the holder finishes, the waiter checks the outcome: if the holder committed, the waiter aborts with a serialization failure, because the row it is about to overwrite has changed under its snapshot; if the holder rolled back, no committed change happened and the waiter proceeds. The observable difference is *when* you learn you lost and whether you burned CPU first. The correctness outcome is identical: at most one of the two concurrent updates to a row survives, and the loser is told. ## The error the loser sees A serialization failure — a distinct error class from constraint violations, deadlocks, or timeouts, and one whose contract is *this transaction can succeed if you run it again*. That contract matters because the correct handling is a bounded retry with backoff at the transaction boundary, which re-runs the reads against a new snapshot. A retry that reuses values read before the abort defeats the purpose entirely: the whole point is that the winner's value must now be observed. Do not confuse this with a deadlock. A deadlock is a cycle of waits that the engine breaks by choosing a victim; a snapshot-isolation write conflict is a unilateral abort caused by an update whose basis has changed. Both are retryable, but they have different causes and different fixes. ## What this prevents: lost update Lost update is the read-modify-write race. Both transactions read `stock = 10`, both compute `10 - 1`, both write `9`, and two units were sold while inventory only dropped by one. Under read committed *without* extra care this is a real risk, because the second write is simply an overwrite of a row the transaction read earlier at a value that has since changed. Snapshot isolation's write-write rule closes it: the second transaction aborts, and on retry it reads `9` and writes `8`. It is worth naming the alternative fixes, because interviewers probe them. An atomic in-place update (`SET stock = stock - 1`) avoids the round trip entirely and is safe even at weaker levels, since the engine re-reads the current row under lock. An explicit locking read taken before the computation serializes the pair by blocking. A compare-and-set update that includes the previously read version in its predicate detects the loss by affecting zero rows. Snapshot isolation gives you the guarantee automatically, at the price of an abort you must handle. ## What this does not prevent: write skew The write-write rule is about a *single* data item. It says nothing when two transactions read overlapping data and write **different** rows: no item is written twice, so no conflict is detected and both commit. That is write skew, and it is exactly why snapshot isolation is not serializable. Candidates who say "first-committer-wins makes SI safe" have collapsed the two cases; the correct framing is that SI validates the *write* set and never validates the *read* set. ## Practical consequences First, hot rows produce abort or block storms. A single counter row updated by every request turns the write-write rule into a throughput ceiling, and the fix is to spread the contention (sharded counters, aggregate on read, or move the counter out of the transaction) rather than to weaken isolation. Second, the abort rate is proportional to transaction length times contention, so long transactions that touch popular rows are the usual culprit in a conflict-heavy system. Third, whichever implementation your engine uses, the application-side contract is the same: catch serialization failures at the transaction boundary and retry the whole unit of work.
- How is a snapshot-isolation write conflict different from a deadlock?A deadlock is a cycle in the wait-for graph: two or more transactions each hold a lock the other needs, and the engine must pick a victim to break the cycle. A write conflict under snapshot isolation involves no cycle — one transaction unilaterally aborts because the row it intends to overwrite was changed by a transaction invisible to its snapshot. Both are transient and retryable, but the diagnosis and the mitigations differ.
- If the losing transaction retries with the same values it read before, what happens?It either re-creates the bug or aborts again. The retry must restart the transaction from the top so the reads are re-issued against a fresh snapshot that includes the winner's write; only then does the recomputed value account for it. This is why the retry belongs at the transaction boundary, wrapping reads and writes together, rather than around the failing statement.
saying these in an interview costs you the question
- Saying first-committer-wins makes snapshot isolation serializable
- Confusing the write-write abort with a deadlock
- Retrying only the failed statement instead of the whole transaction
- Claiming the loser blocks forever rather than aborting
- Believing the rule also covers transactions that write different rows