Version 11.3.0
Transactional API calls

Operations in WiredTiger are inherently transactional and durable when the table is closed, so the checkpoint durability model of making an application transactional and its data durable only requires:

  • Optionally grouping operations into explicit transactions,
  • Optionally adding checkpoint calls for additional durability points.

Explicit transactions

An explicit transaction is a transaction that starts with a begin transaction operation and ends with either a commit transaction or rollback transaction operation. Any operation having multiple related updates should be enclosed in an explicit transaction to ensure the updates are applied atomically.

In WiredTiger, transaction operations are methods of the WT_SESSION class.

Applications call WT_SESSION::begin_transaction to start a new transaction. Operations subsequently performed using that WT_SESSION handle, including operations on any cursors open in that WT_SESSION handle (whether opened before or after the WT_SESSION::begin_transaction call), are part of the transaction. The transaction is committed by calling WT_SESSION::commit_transaction. The effects may be discarded by calling WT_SESSION::rollback_transaction. If WT_SESSION::commit_transaction returns any error, the transaction was rolled back, not committed.

Schema changing operations are not generally transactional in WiredTiger, they can't be grouped together within the scope of a transaction and atomically committed or aborted. Think of them as one-shot transactions where the operation will either succeed or fail. Examples of schema changing operations are table create and drop.

A data operation executed within a transaction can fail if it conflicts with an operation in another concurrently running transaction. (A conflict occurs between two operations when letting both of them continue would lead to a violation of consistency or isolation.) Failures of this form produce the error WT_ROLLBACK. If this error occurs, the application should roll back its current transaction using WT_SESSION::rollback_transaction, and either abandon the operation or retry the entire operation by starting a new transaction.

After a transaction is successfully committed, cursors in the session retain their position, as well as any currently set keys or values they may have. If a transaction is rolled back for any reason, cursors in the session are reset (as if the WT_CURSOR::reset method was called), discarding any cursor position as well as any currently set keys or values.

* Cursors may be opened before or after the transaction begins, and in either case, subsequent
* operations are included in the transaction. Opening cursors before the transaction begins
* allows applications to cache cursors and use them for multiple operations.
error_check(session->open_cursor(session, "table:mytable", NULL, NULL, &cursor));
error_check(session->begin_transaction(session, NULL));
cursor->set_key(cursor, "key");
cursor->set_value(cursor, "value");
switch (cursor->update(cursor)) {
case 0: /* Update success */
error_check(session->commit_transaction(session, NULL));
* If commit_transaction succeeds, cursors remain positioned; if commit_transaction fails,
* the transaction was rolled-back and all cursors are reset.
case WT_ROLLBACK: /* Update conflict */
default: /* Other error */
error_check(session->rollback_transaction(session, NULL));
/* The rollback_transaction call resets all cursors. */
* Cursors remain open and may be used for multiple transactions.

Implicit transactions

While any operation consisting of multiple related updates should be enclosed in an explicit transaction to ensure the updates are applied atomically, it is not necessary to start explicit transactions for single operations. If no explicit transaction is started, each update operation is automatically committed before it returns.

If an implicit transaction successfully commits, the cursors in the WT_SESSION:: remain positioned. If an implicit transaction fails, it is rolled back, and all cursors in the WT_SESSION:: are reset discarding any position or key/value information they may have.

Concurrency control

WiredTiger uses optimistic concurrency control algorithms. Writes do not block other writes, although concurrent transactions updating the same value will fail with WT_ROLLBACK. Some applications may benefit from application-level synchronization to avoid repeated attempts to update and then roll back the same value.

Transactions hold resources, such as cache space, that may be in high demand. Both read and write transactions may fail with the WT_ROLLBACK error if a resource cannot be allocated. For example, if cache resources cannot be allocated to hold the data required to satisfy transactional readers, operations may fail and return WT_ROLLBACK.

A thread with an active transaction should avoid pausing or blocking. This may delay other transactions that require the same resources, leading to performance problems that are hard to diagnose.
As read transactions rarely fail, application developers may neglect to correctly handle a read transaction return of WT_ROLLBACK. Applications must be written to anticipate read failure.

Isolation levels

Three isolation models are supported in WiredTiger, from weaker to stronger:

  • read-uncommitted: Transactions can see changes made by other transactions before those transactions are committed. Dirty reads, non-repeatable reads and phantoms are possible.
  • read-committed: Transactions cannot see changes made by other transactions before those transactions are committed. Dirty reads are not possible; non-repeatable reads and phantoms are possible. Committed changes from concurrent transactions become visible periodically during the lifecycle of the transaction.
  • snapshot: Transactions read the versions of records committed before the transaction started. Dirty reads and non-repeatable reads are not possible; phantoms are possible. Snapshot isolation is the default isolation level, and all updates must be done using snapshot isolation.

Snapshot isolation is recommended in almost all situations, and all updates must be performed using snapshot isolation. Only read operations may be performed at lower isolation levels. In cases where greater read performance is more important than exact consistency, or loss of consistency is acceptable, it can make sense to use a weaker isolation model. If you are unfamiliar with isolation levels in general, or for detailed descriptions of these models, see Tutorial: isolation levels.

The transaction isolation level can be configured on a per-transaction basis.

/* A single transaction configured for snapshot isolation. */
error_check(session->open_cursor(session, "table:mytable", NULL, NULL, &cursor));
error_check(session->begin_transaction(session, "isolation=snapshot"));
cursor->set_key(cursor, "some-key");
cursor->set_value(cursor, "some-value");
error_check(session->commit_transaction(session, NULL));

Additionally, the default isolation level for all transactions can be configured and re-configured on a per-session basis:

/* Open a session configured for read-uncommitted isolation. */
error_check(conn->open_session(conn, NULL, "isolation=read-uncommitted", &session));
/* Re-configure a session for snapshot isolation. */
error_check(session->reconfigure(session, "isolation=snapshot"));

Cursors and transactions

If there is a transaction active in a session, cursors operate in the context of that transaction. Reads performed while a transaction is active operate at the isolation level of the transaction, and updates performed within a transaction are made durable by calling WT_SESSION::commit_transaction, or discarded by calling WT_SESSION::rollback_transaction.

If no transaction is active, cursor reads are performed at the isolation level of the session, set with the isolation configuration key to WT_CONNECTION::open_session, and successful updates are automatically committed before the update operation completes.

At read-committed isolation, committed changes from concurrent transactions become visible only when no cursor is positioned. In other words, at this isolation level, all cursors in a session read from a stable snapshot while any cursor in the session remains positioned. A call to WT_CURSOR::next or WT_CURSOR::prev on a positioned cursor will not update the snapshot.

At snapshot isolation when using implicit transactions, the same behavior applies for reads: when a read is performed, a snapshot is taken, and all cursors will read from that snapshot as long as any cursor remains positioned or until a write operation is performed. Further reads after that point (or a call to WT_SESSION::reset_snapshot) will acquire a new snapshot.

Cursor positions survive transaction boundaries, unless a transaction is rolled back. If a transaction is rolled back for any reason (either implicitly because WT_SESSION::commit_transaction failed, or explicitly because the application called WT_SESSION::rollback_transaction), all cursors in the session are reset as if the WT_CURSOR::reset method was called, discarding any cursor position as well as any key and value that may have been set.

Resetting the session snapshot

Snapshot-isolation transactions can pin large amounts of data into the database cache in order to be able to satisfy potential reads at the snapshot. WT_SESSION::reset_snapshot releases the current snapshot and gets a new (more recent) snapshot to avoid pinning content in the cache that is no longer needed. It is an error to call WT_SESSION::reset_snapshot at any isolation level other than snapshot, or if the current transaction has performed any write operations.

Applications not using read timestamps for search may see different results after the snapshot is updated.
* Get a new read snapshot for the current transaction. This is only permitted for
* transactions running with snapshot isolation.
const char *value1, *value2; /* For the cursor's string value. */
error_check(session->open_cursor(session, "table:mytable", NULL, NULL, &cursor));
error_check(session->begin_transaction(session, "isolation=snapshot"));
cursor->set_key(cursor, "some-key");
error_check(cursor->get_value(cursor, &value1));
error_check(cursor->get_value(cursor, &value2)); /* May be different. */
error_check(session->commit_transaction(session, NULL));
int rollback_transaction(WT_SESSION *session, const char *config)
Roll back the current transaction.
Conflict between concurrent operations.
int open_cursor(WT_SESSION *session, const char *uri, WT_CURSOR *to_dup, const char *config, WT_CURSOR **cursorp)
Open a new cursor on a data source or duplicate an existing cursor.
int search(WT_CURSOR *cursor)
Return the record matching the key.
int commit_transaction(WT_SESSION *session, const char *config)
Commit the current transaction.
int open_session(WT_CONNECTION *connection, WT_EVENT_HANDLER *event_handler, const char *config, WT_SESSION **sessionp)
Open a session.
int get_value(WT_CURSOR *cursor,...)
Get the value for the current record.
int begin_transaction(WT_SESSION *session, const char *config)
Start a transaction in this session.
void set_value(WT_CURSOR *cursor,...)
Set the value for the next operation.
int reconfigure(WT_SESSION *session, const char *config)
Reconfigure a session handle.
int update(WT_CURSOR *cursor)
Update an existing record and optionally insert a record.
void set_key(WT_CURSOR *cursor,...)
Set the key for the next operation.
int reset_snapshot(WT_SESSION *session)
Reset the snapshot used for database visibility.