Skip to main content
The Transaction struct provides ACID transaction support with snapshot isolation and optimistic concurrency control.

Creating transactions

Transactions are created using Database::begin() or Database::run_transaction().
let mut tx = db.begin()?;
// Perform operations...
tx.commit()?;
For automatic retry on conflicts:
db.run_transaction(|tx| {
    // Perform operations...
    Ok(())
})?;

Collection operations

collection

Get a collection handle within the transaction.
pub fn collection(&mut self, name: &str) -> Result<TxCollection<'_>>
name
&str
required
Name of the collection
Returns: Result<TxCollection> - A transactional collection handle Example:
let mut tx = db.begin()?;
let mut users = tx.collection("users")?;
users.insert(json!({"name": "Alice"}))?;
tx.commit()?;

create_collection

Create a new collection within the transaction.
pub fn create_collection(&mut self, name: &str) -> Result<()>
name
&str
required
Name of the collection to create
Example:
let mut tx = db.begin()?;
tx.create_collection("orders")?;
tx.commit()?;

drop_collection

Drop (delete) a collection and all its documents.
pub fn drop_collection(&mut self, name: &str) -> Result<()>
name
&str
required
Name of the collection to drop
Example:
let mut tx = db.begin()?;
tx.drop_collection("temp_data")?;
tx.commit()?;

rename_collection

Rename a collection.
pub fn rename_collection(&mut self, old_name: &str, new_name: &str) -> Result<()>
old_name
&str
required
Current name of the collection
new_name
&str
required
New name for the collection
Example:
let mut tx = db.begin()?;
tx.rename_collection("users", "customers")?;
tx.commit()?;

Transaction control

commit

Commit the transaction, making all changes permanent.
pub fn commit(&mut self) -> Result<()>
Returns: Result<()> - Ok if committed successfully, or Error::TxConflict if there was a write conflict Example:
let mut tx = db.begin()?;
// Perform operations...
match tx.commit() {
    Ok(_) => println!("Transaction committed"),
    Err(Error::TxConflict) => println!("Conflict detected, retry"),
    Err(e) => println!("Error: {}", e),
}

rollback

Rollback the transaction, discarding all changes.
pub fn rollback(&mut self) -> Result<()>
Example:
let mut tx = db.begin()?;
// Perform operations...
if should_cancel {
    tx.rollback()?;
} else {
    tx.commit()?;
}

is_active

Check if the transaction is still active.
pub fn is_active(&self) -> bool
Returns: bool - true if active, false if committed or rolled back

Transaction properties

Each transaction has the following properties:
tx_id
u64
Unique transaction ID
mvcc_tx_id
TransactionID
MVCC transaction ID for versioning
snapshot_id
TransactionID
Snapshot ID representing the transaction’s view of the database
state
TxState
Current transaction state (Active, Committed, or RolledBack)

Error handling

Transactions can fail for several reasons:

Write conflicts

Occur when two transactions try to modify the same document:
let result = tx.commit();
match result {
    Err(Error::TxConflict) => {
        // Another transaction modified the same data
        // Retry the transaction
    }
    Ok(_) => println!("Success"),
    Err(e) => println!("Other error: {}", e),
}

Automatic retry

Use run_transaction for automatic retry with exponential backoff:
let result = db.run_transaction(|tx| {
    let mut users = tx.collection("users")?;
    users.insert(json!({"name": "Bob"}))?;
    Ok(())
});

// Automatically retries up to max_retries times on conflicts

Best practices

Keep transactions short

Transactions hold locks and resources. Complete them quickly:
// Good: short transaction
db.run_transaction(|tx| {
    let mut users = tx.collection("users")?;
    users.update_by_id("user_123", json!({"status": "active"}))?;
    Ok(())
})?;

// Avoid: long-running transaction
db.run_transaction(|tx| {
    // Don't do expensive I/O or computation here
    let data = fetch_from_external_api()?; // BAD
    thread::sleep(Duration::from_secs(10)); // BAD
    Ok(())
})?;

Handle conflicts gracefully

Use run_transaction for automatic retry, or implement custom retry logic:
let mut attempts = 0;
let max_attempts = 5;

loop {
    let mut tx = db.begin()?;
    
    match perform_update(&mut tx) {
        Ok(_) => match tx.commit() {
            Ok(_) => break,
            Err(Error::TxConflict) if attempts < max_attempts => {
                attempts += 1;
                thread::sleep(Duration::from_millis(10 * attempts));
                continue;
            }
            Err(e) => return Err(e),
        },
        Err(e) => {
            tx.rollback()?;
            return Err(e);
        }
    }
}

Always commit or rollback

Transactions that are neither committed nor rolled back will be automatically rolled back when dropped:
{
    let mut tx = db.begin()?;
    let mut users = tx.collection("users")?;
    users.insert(json!({"name": "Alice"}))?;
    // Transaction is rolled back here when `tx` goes out of scope
}

// Explicitly commit:
{
    let mut tx = db.begin()?;
    let mut users = tx.collection("users")?;
    users.insert(json!({"name": "Alice"}))?;
    tx.commit()?; // Changes are persisted
}

Snapshot isolation

jasonisnthappy provides snapshot isolation:
  • Each transaction sees a consistent snapshot of the database
  • Changes made by other transactions are not visible
  • Write conflicts are detected at commit time
// Transaction 1
let mut tx1 = db.begin()?;
let users1 = db.collection("users");
let count1 = users1.count()?; // Sees snapshot at tx1 start time

// Transaction 2 inserts a document
let users2 = db.collection("users");
users2.insert(json!({"name": "New User"}))?;

// Transaction 1 still sees the old count
let count_still = users1.count()?; // Same as count1
tx1.commit()?;

Build docs developers (and LLMs) love