Date: 2026-05-29
Time: 10:07
MVCCDatabase.commit — Transaction Commit with Write-Write Conflict Detectioncommit finalizes a transaction, making its writes durable and visible to future transactions. Before committing a read-write transaction, it performs first-committer-wins conflict detection: if another transaction wrote to any of the same keys and committed during this transaction's lifetime, the current transaction is aborted instead. This is the core mechanism that enforces snapshot isolation — each transaction operates on a consistent snapshot, and concurrent writes to the same key are not allowed to silently overwrite each other.
Preconditions:
tx must be an active transaction (status "active"). Calling commit on an already-committed or aborted transaction raises TransactionError.Postconditions (on success):
tx._status is "committed"tx.txid is in self.committedself.committimestampsPostconditions (on conflict):
tx is aborted via self.abort(tx) — status becomes "aborted", write-set side effects (deletions) are rolled backFalseInvariant: At most one transaction that wrote to a given key can commit per conflict window. This prevents lost updates.
| Parameter | Type | Description |
|-----------|------|-------------|
| tx | Transaction | The transaction to commit. Must have been returned by begin_transaction. |
Returns bool:
True — commit succeeded, writes are now visible to future snapshotsFalse — write-write conflict detected, transaction was abortedThe caller must check this return value. After False, the transaction is dead — any further operations on it will raise TransactionError.
1. Guard: Verify the transaction is still active.
2. Fast path for read-only transactions: If tx.read_only, skip conflict detection entirely — read-only transactions can never conflict. Mark committed immediately.
3. Write-write conflict detection: For each key in the transaction's write set, scan all versions of that key looking for a conflicting version. A version v is conflicting if all of the following hold:
v.createdby != tx.txid — it was written by a *different* transactionv.createdby not in self.aborted — that transaction wasn't abortedv.createdby in self.committed — that transaction has committedv.createdby in tx.activeatstart or v.createdby >= tx.tx_id — the creating transaction was either still active when we started (meaning it committed *during* our lifetime) or it started after us (meaning it's entirely concurrent)The last condition is the key insight: if a transaction committed *before* we started and wasn't in our active set, its write is part of our snapshot — that's not a conflict. A conflict only exists when a concurrent transaction committed a write to the same key.
On conflict, the method calls self.abort(tx) and returns False.
4. Commit: Set status to "committed", add to the committed set, allocate a monotonically increasing commit timestamp, and store it.
tx._status — sets to "committed" or (via abort) "aborted"self._committed — adds the transaction IDself.committimestamps — records the commit timestampself.nexttimestamp — allocates a commit timestampself.abort(tx) — which also rolls back deletion markers in the version chainsTransactionError if the transaction is not active (already committed or aborted)False and abort silently. This is a design choice: conflicts are expected in concurrent workloads and are part of normal control flow.
db = MVCCDatabase()
tx = db.begin_transaction()
db.write(tx, "account:1", 500)
if not db.commit(tx):
# Conflict — retry with a new transaction
tx = db.begin_transaction()
# ... re-read and re-apply logic
Callers are expected to implement retry loops around commit failures. The abort-on-conflict pattern means the caller never needs to manually call abort after a failed commit.
self.abort(tx) — delegates rollback on conflictself.checkactive(tx) — precondition guardTransaction.write_set — the set of keys written, populated by write() and delete()Transaction.activeatstart — snapshot of active transaction IDs at begin time, set by begin_transaction()self.versions, self.committed, self._aborted — global MVCC state1. Single-threaded execution. There's no locking — the conflict check and commit are not atomic. In a multi-threaded environment, two transactions could both pass the conflict check before either commits, violating first-committer-wins.
2. The conflict check scans *all* versions of each key, not just the latest. This is O(versions) per key in the write set, which could degrade if versions accumulate without garbage collection.
3. The conflict condition v.createdby >= tx.txid catches transactions that started after us, but this relies on monotonically increasing transaction IDs correlating with temporal ordering — which holds because begintransaction increments nexttxid sequentially.
4. Commit timestamps are separate from transaction IDs and start timestamps. A transaction gets a start timestamp at begin time and a different commit timestamp at commit time. This two-timestamp design supports the visibility rules in isvisible, though notably the commit timestamp isn't actually used by isvisible — visibility is determined purely by transaction IDs and the committed/active sets.
commit-first-committer-wins — Write-write conflicts are resolved by first-committer-wins: if two transactions write to the same key, the one that commits first succeeds and the second is abortedcommit-read-only-skip-conflict-check — Read-only transactions bypass conflict detection entirely and always commit successfullycommit-timestamp-unused-by-visibility — Commit timestamps are assigned and stored but never referenced by isvisible; visibility uses transaction IDs and the committed/active-at-start sets insteadcommit-aborts-on-conflict — On conflict, commit calls abort internally before returning False, so the caller never needs to manually abort after a failed commitcommit-single-threaded-assumption — The conflict-check-then-commit sequence is not atomic; correct behavior assumes single-threaded execution with no concurrent calls to commit