Implement ACID transactions in RocksDB for consistent data operations
RocksDB provides transaction support with snapshot isolation, enabling atomic, consistent, isolated, and durable (ACID) operations. This guide covers both pessimistic and optimistic transaction implementations.
using ROCKSDB_NAMESPACE::Transaction;using ROCKSDB_NAMESPACE::WriteOptions;using ROCKSDB_NAMESPACE::ReadOptions;WriteOptions write_options;ReadOptions read_options;std::string value;// Start a transactionTransaction* txn = txn_db->BeginTransaction(write_options);assert(txn);// Read a key in this transactionStatus s = txn->Get(read_options, "abc", &value);assert(s.IsNotFound());// Write a key in this transactions = txn->Put("abc", "def");assert(s.ok());// Commit transactions = txn->Commit();assert(s.ok());delete txn;// Value is committed, can be read nows = txn_db->Get(read_options, "abc", &value);assert(s.ok());assert(value == "def");
Pessimistic transactions use locks to prevent conflicts:
// Transaction 1Transaction* txn1 = txn_db->BeginTransaction(write_options);txn1->Put("abc", "def");// Transaction 2 tries to write the same key// This will fail with kLockTimeoutStatus s = txn_db->Put(write_options, "abc", "xyz");assert(s.subcode() == Status::kLockTimeout);// Transaction 1 commitstxn1->Commit();delete txn1;// Now transaction 2 can proceeds = txn_db->Put(write_options, "abc", "xyz");assert(s.ok());
Writes outside of a transaction will block if a transaction holds a lock on the same key.
using ROCKSDB_NAMESPACE::TransactionOptions;using ROCKSDB_NAMESPACE::Snapshot;// Set a snapshot at start of transactionTransactionOptions txn_options;txn_options.set_snapshot = true;Transaction* txn = txn_db->BeginTransaction(write_options, txn_options);const Snapshot* snapshot = txn->GetSnapshot();// Write OUTSIDE of transactionStatus s = txn_db->Put(write_options, "abc", "xyz");assert(s.ok());// Read the latest committed value (without snapshot)std::string value;s = txn->Get(read_options, "abc", &value);assert(s.ok());assert(value == "xyz");// Read the snapshotted valueread_options.snapshot = snapshot;s = txn->Get(read_options, "abc", &value);assert(s.ok());assert(value == "def"); // Old value from snapshot// Attempt to write will fail due to conflicts = txn->GetForUpdate(read_options, "abc", &value);assert(s.IsBusy());txn->Rollback();delete txn;
Use set_snapshot = true when you need consistent reads across multiple operations in a transaction.
Save points allow partial rollback within a transaction:
Transaction* txn = txn_db->BeginTransaction(write_options);// Do some writestxn->Put("x", "x");// Set a save pointtxn->SetSavePoint();// Do more writestxn->Put("y", "y2");// Rollback to save pointtxn->RollbackToSavePoint();// Commit - only "x" is written, "y" is notStatus s = txn->Commit();assert(s.ok());delete txn;
OptimisticTransactionOptions txn_options;Transaction* txn = txn_db->BeginTransaction(write_options, txn_options);// Read a keystd::string value;Status s = txn->Get(read_options, "abc", &value);// Write a keys = txn->Put("abc", "xyz");assert(s.ok());// Write OUTSIDE the transaction to the same key// This does NOT block (unlike pessimistic transactions)DB* db = txn_db->GetBaseDB();s = db->Put(write_options, "abc", "def");assert(s.ok());// Commit will fail because of the conflicts = txn->Commit();assert(s.IsBusy()); // Conflict detected at commit timedelete txn;
Optimistic transactions detect conflicts only at commit time. The commit will fail with Status::Busy() if a conflict is detected.