Function: commit in snapshot-isolation/mvcc_database.py

Date: 2026-05-29

Time: 10:07

MVCCDatabase.commit — Transaction Commit with Write-Write Conflict Detection

Purpose

commit 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.

Contract

Preconditions:

Postconditions (on success):

Postconditions (on conflict):

Invariant: At most one transaction that wrote to a given key can commit per conflict window. This prevents lost updates.

Parameters

| Parameter | Type | Description |

|-----------|------|-------------|

| tx | Transaction | The transaction to commit. Must have been returned by begin_transaction. |

Return Value

Returns bool:

The caller must check this return value. After False, the transaction is dead — any further operations on it will raise TransactionError.

Algorithm

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:

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.

Side Effects

Error Handling

Usage Patterns


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.

Dependencies

Notable Assumptions

1. 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.

Beliefs