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<'_>>
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 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 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<()>
Current name of the collection
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:
MVCC transaction ID for versioning
Snapshot ID representing the transaction’s view of the database
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()?;