# How Turso's Transaction State Machine Handles Isolation Levels

> Discover how Turso's transaction state machine enforces snapshot isolation using immutable timestamps, multiversion validation, and eager conflict detection for robust data integrity.

- Repository: [Turso Database/turso](https://github.com/tursodatabase/turso)
- Tags: internals
- Published: 2026-06-23

---

**Turso's transaction state machine enforces snapshot isolation as its sole MVCC isolation level by assigning immutable begin timestamps to transactions, validating reads against multiversion chains, and detecting write-write conflicts eagerly before commit.**

Turso is an edge-hosted distributed database that extends SQLite with a multiversion concurrency-control (MVCC) storage engine. While traditional SQL databases offer configurable isolation levels like `READ COMMITTED` or `SERIALIZABLE`, Turso's architecture simplifies concurrency control by implementing snapshot isolation exclusively for all MVCC-enabled transactions. The transaction state machine—comprising the `Transaction` and `CommitStateMachine` types—orchestrates this behavior through deterministic state transitions in [`core/mvcc/database/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/database/mod.rs).

## Snapshot Isolation: The Only MVCC Level

Turso does not expose `READ COMMITTED`, `REPEATABLE READ`, or `SERIALIZABLE` as user-selectable isolation modes. Instead, every transaction initiated with `BEGIN CONCURRENT` operates under snapshot isolation semantics, backed by logical timestamp ordering rather than locking.

### The TransactionState Enum

At the core of the state machine is the `TransactionState` enum. This tracks the transaction lifecycle through discrete phases:

```rust
enum TransactionState {
    Active,                     // Initial state with assigned begin_ts
    Preparing(u64),             // Commit in progress, end_ts allocated
    Committed(u64),             // Final timestamp, visible globally
    Aborted,
    Terminated,
}

```

When a client executes `BEGIN CONCURRENT`, the system creates a `Transaction` instance in the `Active` state with a `begin_ts` drawn from the `MvccClock` defined in [`core/mvcc/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/mod.rs).

## Enforcing Snapshot Isolation Properties

The state machine guarantees snapshot isolation through three coordinated mechanisms: immutable read snapshots, version visibility control, and eager conflict detection.

### Consistent Snapshot Reads

When a transaction starts, it captures a **begin timestamp** (`begin_ts`) from the logical clock. All subsequent reads via `MvStore::read` inspect the version chain and return a row only if the transaction's `begin_ts` falls between the version's `begin` and `end` timestamps. This snapshot remains immutable for the transaction's lifetime.

In [`core/mvcc/database/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/database/mod.rs), the visibility check uses the `TxTimestampOrID` type to distinguish between committed timestamps and active transaction IDs:

```rust
// Simplified visibility logic from MvStore::read
if version.begin_ts <= tx.begin_ts && version.end_ts > tx.begin_ts {
    return Ok(Some(version)); // Visible in snapshot
}

```

### Preventing Dirty Writes

Write operations remain invisible to other transactions until commit completes. When a transaction modifies a row, `MvStore::upsert`, `insert`, or `delete` assigns the writer's `TxID` (rather than a timestamp) to the new version's `begin` field. The read path automatically filters out any version whose `begin` value is a `TxID`, effectively blocking dirty reads while allowing the writer to see its own uncommitted changes.

### Eager Write-Write Conflict Detection

Turso detects conflicts immediately rather than at commit time. Before writing, the system checks if the target row has an uncommitted version via `row_has_uncommitted_version_for_tx`. If another active transaction has modified the row, the operation returns `LimboError::WriteWriteConflict`:

```rust
// From core/mvcc/database/mod.rs
if self.row_has_uncommitted_version_for_tx(&key, tx_id) {
    return Err(LimboError::WriteWriteConflict);
}

```

This prevents serialization anomalies (G0 cycles) and eliminates the need for blocking locks during the commit phase.

## The Commit State Machine

When `COMMIT` executes, the transaction transitions from `Active` to `Preparing(end_ts)`, where `end_ts` is allocated from the `MvccClock`. The `CommitStateMachine` in [`core/mvcc/database/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/database/mod.rs) then executes a deterministic sequence: `Initial → Commit → WaitForDependencies → BuildLogRecord → RewriteLiveVersions → FinalizeCommit`.

During the `RewriteLiveVersions` phase, temporary `TxID` markers in the version chain are atomically replaced with the final `Timestamp`, making the changes visible to transactions with higher `begin_ts` values.

## Practical Transaction Lifecycle

The following example demonstrates how snapshot isolation behaves across concurrent connections:

```rust
// Transaction 1: Start snapshot-isolated transaction
let conn1 = db.connect();
conn1.execute("BEGIN CONCURRENT").unwrap(); // State: Active, begin_ts = 42
let rows = conn1.query("SELECT value FROM t WHERE id = 1", ()).unwrap();
assert_eq!(rows[0].get_int(0).unwrap(), 10); // Sees snapshot value

// Transaction 2: Attempt concurrent write to same row
let conn2 = db.connect();
conn2.execute("BEGIN CONCURRENT").unwrap();
let err = conn2.execute("UPDATE t SET value = 20 WHERE id = 1");
assert!(matches!(err, Err(LimboError::WriteWriteConflict))); // Eager conflict

// Transaction 1: Commit through state machine
conn1.execute("COMMIT").unwrap(); // Active → Preparing(43) → Committed(43)

// Transaction 3: Sees committed value with new snapshot
let conn3 = db.connect();
conn3.execute("BEGIN CONCURRENT").unwrap(); // begin_ts = 44
let rows = conn3.query("SELECT value FROM t WHERE id = 1", ()).unwrap();
assert_eq!(rows[0].get_int(0).unwrap(), 20); // Sees timestamped version

```

## Fallback to SQLite Locking

Transactions started with plain `BEGIN` (without the `CONCURRENT` keyword) bypass the MVCC state machine entirely. These sessions use SQLite's native page-level locking, which behaves similarly to **read committed** isolation. Only transactions using `BEGIN CONCURRENT` participate in the snapshot isolation system.

## Key Source Files

The implementation spans these critical paths in the `tursodatabase/turso` repository:

- **[`core/mvcc/database/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/database/mod.rs)**: Defines `Transaction`, `TransactionState`, `CommitStateMachine`, and the MVCC read/write paths that enforce snapshot isolation.
- **[`core/mvcc/database/hermitage_tests.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/database/hermitage_tests.rs)**: Comprehensive test suite validating snapshot isolation behavior including write skew and phantom prevention.
- **[`core/connection.rs`](https://github.com/tursodatabase/turso/blob/main/core/connection.rs)**: Entry point for `BEGIN CONCURRENT` command parsing and transaction initialization.
- **[`core/mvcc/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/mvcc/mod.rs)**: Implements `MvccClock`, `PackedTs`, and `TxTimestampOrID` auxiliary types.

## Summary

- Turso's transaction state machine supports **snapshot isolation exclusively** for MVCC transactions; no other isolation levels are selectable.
- The `TransactionState` enum tracks atomic progress from `Active` through `Preparing` to `Committed`.
- **Begin timestamps** (`begin_ts`) create immutable read snapshots for the transaction duration using the `MvccClock`.
- **Write-write conflicts** are detected eagerly via `MvStore` methods that return `LimboError::WriteWriteConflict` immediately.
- The `CommitStateMachine` coordinates commit ordering through deterministic state transitions ending with `RewriteLiveVersions`.
- Non-concurrent transactions fall back to SQLite's page-level locking without MVCC semantics.

## Frequently Asked Questions

### Does Turso support the SERIALIZABLE isolation level?

No, Turso intentionally does not implement `SERIALIZABLE`, `READ COMMITTED`, or `REPEATABLE READ` as distinct isolation levels. The MVCC subsystem in `core/mvcc/` provides snapshot isolation exclusively, which prevents write skew and phantom reads without the performance overhead of strict serializable schedules.

### What happens when two transactions try to modify the same row?

The second transaction receives an immediate `LimboError::WriteWriteConflict` error when executing the write statement. This eager conflict detection occurs inside `MvStore::upsert` and related methods, preventing lost updates without requiring row locking or blocking waits.

### How does Turso handle read-only transactions?

Read-only transactions remain in the `Active` state for their entire session. Since they never modify data, they do not invoke the `CommitStateMachine`, but they still retain their initial `begin_ts` snapshot to guarantee repeatable reads against the version chain stored in the MVCC engine.

### What is the difference between BEGIN and BEGIN CONCURRENT?

`BEGIN` uses SQLite's traditional page-level locking and behaves like read committed isolation, while `BEGIN CONCURRENT` initiates an MVCC transaction with snapshot isolation. Only `CONCURRENT` transactions participate in the version chain system, timestamp-based visibility checks, and the `TransactionState` machine.