Skip to main content
Version: 3.1.0 (Latest)

Transactions API

The Transactions API provides explicit transaction control for operations across tables and SQL statements. Transactions ensure atomic, consistent, isolated, and durable data modifications.

Key Concepts

Transaction Lifecycle

Transactions begin through the transactions factory. They remain active until committed or rolled back. Operations within a transaction see uncommitted changes from that transaction. Other transactions see data as it existed before the transaction started.

Explicit vs Implicit Transactions

Explicit transactions require manual commit or rollback. Pass the transaction pointer to operations. This provides control over transaction boundaries.

Implicit transactions commit automatically after each operation. Pass nullptr to operations for implicit transactions.

Transaction Isolation

Transactions use snapshot isolation. Each transaction sees a consistent snapshot of data from transaction start time. Changes within a transaction are visible to that transaction but not to others until commit.

Transaction Options

Configure transaction behavior through options:

  • timeout - Maximum transaction duration
  • read_only - Optimize for read-only workloads

Basic Usage

Beginning Transactions

Start a transaction with default options:

using namespace ignite;

auto transactions = client.get_transactions();
auto tx = transactions.begin();

Start with options:

transaction_options opts;
opts.set_timeout_millis(30000); // 30 seconds
opts.set_read_only(false);

auto tx = transactions.begin(opts);

Use async begin:

transactions.begin_async([](ignite_result<transaction> result) {
if (!result.has_error()) {
auto tx = std::move(result).value();
// Use transaction
}
});

Committing Transactions

Commit to persist changes:

auto tx = transactions.begin();

// Perform operations
tx.commit();

Use async commit:

tx.commit_async([](ignite_result<void> result) {
if (!result.has_error()) {
std::cout << "Transaction committed" << std::endl;
}
});

Rolling Back Transactions

Rollback to discard changes:

auto tx = transactions.begin();

try {
// Perform operations
tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

Use async rollback:

tx.rollback_async([](ignite_result<void> result) {
if (!result.has_error()) {
std::cout << "Transaction rolled back" << std::endl;
}
});

Table Operations

Using Transactions with Record Views

Pass transaction pointer to record view operations:

auto tx = client.get_transactions().begin();

auto table = client.get_tables().get_table("accounts").value();
auto view = table.get_record_binary_view();

try {
ignite_tuple record{
{"id", 42},
{"name", "John Doe"},
{"balance", 1000.0}
};

view.upsert(&tx, record);

auto retrieved = view.get(&tx, ignite_tuple{{"id", 42}});

tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

Using Transactions with Key-Value Views

Pass transaction to key-value operations:

auto tx = client.get_transactions().begin();

auto table = client.get_tables().get_table("accounts").value();
auto view = table.get_key_value_binary_view();

try {
ignite_tuple key{{"id", 42}};
ignite_tuple value{{"name", "John Doe"}, {"balance", 1000.0}};

view.put(&tx, key, value);
auto retrieved = view.get(&tx, key);

tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

Batch Operations

Batch operations execute within a transaction:

auto tx = client.get_transactions().begin();

std::vector<ignite_tuple> records{
{{"id", 1}, {"name", "Alice"}, {"balance", 1000.0}},
{{"id", 2}, {"name", "Bob"}, {"balance", 2000.0}},
{{"id", 3}, {"name", "Charlie"}, {"balance", 3000.0}}
};

try {
view.upsert_all(&tx, records);
tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

SQL Operations

Executing SQL in Transactions

Pass transaction to SQL operations:

auto tx = client.get_transactions().begin();

try {
auto sql = client.get_sql();

sql.execute(&tx, nullptr,
sql_statement("INSERT INTO accounts VALUES (?, ?, ?)"),
{42, std::string("John Doe"), 1000.0});

sql.execute(&tx, nullptr,
sql_statement("UPDATE accounts SET balance = ? WHERE id = ?"),
{1500.0, 42});

tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

Cross-Table Transactions

Execute operations across multiple tables:

auto tx = client.get_transactions().begin();

try {
auto sql = client.get_sql();

// Debit from one account
sql.execute(&tx, nullptr,
sql_statement("UPDATE accounts SET balance = balance - ? WHERE id = ?"),
{100.0, 1});

// Credit to another account
sql.execute(&tx, nullptr,
sql_statement("UPDATE accounts SET balance = balance + ? WHERE id = ?"),
{100.0, 2});

tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
throw;
}

Transaction Options

Configuring Timeout

Set maximum transaction duration:

transaction_options opts;
opts.set_timeout_millis(60000); // 60 seconds

auto tx = transactions.begin(opts);

Timeout of 0 means no timeout:

opts.set_timeout_millis(0);  // No timeout

Read-Only Transactions

Optimize for read operations:

transaction_options opts;
opts.set_read_only(true);

auto tx = transactions.begin(opts);

// Only read operations allowed
auto result = view.get(&tx, key);

tx.commit(); // Lightweight commit for read-only

Read-only transactions provide better performance by avoiding write locks and conflict detection.

Chaining Options

Use fluent API to chain option setters:

transaction_options opts;
opts.set_timeout_millis(30000)
.set_read_only(false);

auto tx = transactions.begin(opts);

Transaction Visibility

Uncommitted Changes

Changes are visible within the transaction:

auto tx = transactions.begin();

view.upsert(&tx, record);

// This sees the upserted record
auto result = view.get(&tx, key);

// Other transactions do not see it yet

Isolation from Other Transactions

Each transaction sees a consistent snapshot:

// Transaction 1
auto tx1 = transactions.begin();
view.upsert(&tx1, record1);

// Transaction 2 (concurrent)
auto tx2 = transactions.begin();
auto result = view.get(&tx2, key); // Does not see record1

tx1.commit();

// Transaction 2 still does not see record1 (snapshot isolation)
auto result2 = view.get(&tx2, key); // Still does not see record1

Asynchronous Transactions

Async Begin

Start transactions asynchronously:

transactions.begin_async([&](ignite_result<transaction> result) {
if (!result.has_error()) {
auto tx = std::move(result).value();

view.upsert_async(&tx, record, [&](ignite_result<void> upsert_result) {
if (!upsert_result.has_error()) {
tx.commit_async([](ignite_result<void> commit_result) {
// Transaction committed
});
}
});
}
});

Async Begin with Options

Pass options to async begin:

transaction_options opts;
opts.set_timeout_millis(30000);

transactions.begin_async(opts, [](ignite_result<transaction> result) {
// Use transaction
});

Error Handling

Handling Commit Failures

Commit failures indicate conflicts or constraints:

auto tx = transactions.begin();

try {
view.upsert(&tx, record);
tx.commit();
} catch (const ignite_error& e) {
std::cerr << "Commit failed: " << e.what_str() << std::endl;
// Transaction already rolled back on commit failure
throw;
}

Handling Operation Failures

Roll back on operation errors:

auto tx = transactions.begin();

try {
view.upsert(&tx, record1);
view.upsert(&tx, record2); // May throw
tx.commit();
} catch (const ignite_error& e) {
tx.rollback();
std::cerr << "Operation failed: " << e.what_str() << std::endl;
throw;
}

Timeout Handling

Transactions time out after configured duration:

transaction_options opts;
opts.set_timeout_millis(1000); // 1 second

auto tx = transactions.begin(opts);

try {
// Long-running operation
std::this_thread::sleep_for(std::chrono::seconds(2));
tx.commit(); // Will fail due to timeout
} catch (const ignite_error& e) {
// Handle timeout error
}

Best Practices

Keep Transactions Short

Minimize transaction duration to reduce conflicts:

// Good: Short transaction
auto tx = transactions.begin();
view.upsert(&tx, record);
tx.commit();

// Avoid: Long-running transaction
auto tx2 = transactions.begin();
perform_expensive_calculation(); // Do outside transaction
view.upsert(&tx2, result);
tx2.commit();

Use Read-Only for Queries

Enable read-only optimization:

transaction_options opts;
opts.set_read_only(true);

auto tx = transactions.begin(opts);
auto results = view.get_all(&tx, keys);
tx.commit();

Handle Errors Properly

Always rollback on errors:

auto tx = transactions.begin();
bool committed = false;

try {
// Operations
tx.commit();
committed = true;
} catch (const ignite_error& e) {
if (!committed) {
tx.rollback();
}
throw;
}

Use RAII for Automatic Cleanup

Wrap transactions in RAII helpers:

class transaction_guard {
transaction& tx_;
bool committed_ = false;

public:
explicit transaction_guard(transaction& tx) : tx_(tx) {}

~transaction_guard() {
if (!committed_) {
try {
tx_.rollback();
} catch (...) {
// Log error
}
}
}

void commit() {
tx_.commit();
committed_ = true;
}
};

// Usage
auto tx = transactions.begin();
transaction_guard guard(tx);

view.upsert(&tx, record);
guard.commit(); // Automatic rollback if not committed

Reference