Transactions provide a powerful abstraction for multiple threads to operate on data concurrently because they have the following properties:
WiredTiger supports transactions with the following caveats to the ACID properties:
In WiredTiger, transaction operations are methods off the Session class.
Applications call Session.begin_transaction to start a new transaction. Operations subsequently performed using that Session handle, including operations on any cursors open in that Session handle (whether opened before or after the Session.begin_transaction call), are part of the transaction and their effects committed by calling Session.commit_transaction, or discarded by calling Session.rollback_transaction.
If Session.commit_transaction returns an error for any reason, the transaction was rolled back, not committed.
When transactions are used, data operations can encounter a conflict and fail with the WT_ROLLBACK error. If this error occurs, transactions should be rolled back with Session.rollback_transaction and the operation retried.
The Session.rollback_transaction method implicitly resets all cursors in the session as if the Cursor.reset method was called, discarding any cursor position as well as any key and value.
If a cursor is used when no explicit transaction is active in a session, reads are performed at the isolation level of the session, set with the isolation
key to Connection.open_session, and successful updates are automatically committed before the update operation returns.
Any operation consisting of multiple related updates should be enclosed in an explicit transaction to ensure the updates are applied atomically.
If an implicit transaction successfully commits, the cursors in the Session remain positioned. If an implicit transaction fails, all cursors in the Session are reset, as if Cursor.reset were called, discarding any position or key/value information they may have.
See Cursors and Transactions for more information.
WiredTiger uses optimistic concurrency control algorithms. This avoids the bottleneck of a centralized lock manager and ensures transactional operations do not block: reads do not block writes, and vice versa.
Further, writes do not block 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 rollback and update the same value.
Operations in transactions may also fail with the WT_ROLLBACK error if some resource cannot be allocated after repeated attempts. For example, if the cache is not large enough to hold the updates required to satisfy transactional readers, an operation may fail and return WT_ROLLBACK.
WiredTiger supports read-uncommitted
, read-committed
and snapshot
isolation levels; the default isolation level is read-committed
.
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 when no cursor is positioned in the read-committed 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.The transaction isolation level can be configured on a per-transaction basis:
Additionally, the default transaction isolation can be configured and re-configured on a per-session basis:
Applications can create named snapshots by calling Session.snapshot with a configuration that includes "name=foo"
. This configuration creates a new named snapshot, as if a snapshot isolation transaction were started at the time of the Session.snapshot call.
Subsequent transactions can be started "as of" that snapshot by calling Session.begin_transaction with a configuration that includes snapshot=foo
. That transaction will run at snapshot isolation as if the transaction started at the time of the Session.snapshot call that created the snapshot.
Named snapshots keep data pinned in cache as if a real transaction were running for the time that the named snapshot is active. The resources associated with named snapshots should be released by calling Session.snapshot with a configuration that includes "drop="
. See Session.snapshot documentation for details of the semantics supported by the drop configuration.
Named snapshots are not durable: they do not survive Connection.close.
Some applications have their own notion of time, including an expected commit order for transactions that may be inconsistent with the order assigned by WiredTiger. We assume that applications can represent their notion of a timestamp as an unsigned integral value of some size that generally increases over time. For example, a simple 64-bit integer could be incremented to generate transaction timestamps, if that is sufficient for the application.
The application's timestamp size is specified as a number of bytes at build time, with configure –with-timestamp-size=X
. The default timestamp size is 8 bytes (i.e., 64 bits). Setting a size of zero disables transaction timestamp functionality.
Applications can assign explicit commit timestamps to transactions, then read "as of" a timestamp. Timestamps are communicated to WiredTiger using a hexadecimal encoding, so the encoded value can be twice as long as the raw timestamp value.
WiredTiger also provides the ability to set a different commit timestamp for different set of updates in a single transaction. This can be done by calling Session.timestamp_transaction repeatedly to set a new commit timestamp between a set of updates for the current transaction. This gives the ability to commit several updates with different read "as of" timestamp in a single transaction.
Setting a read timestamp in Session.begin_transaction forces a transaction to run at snapshot isolation and ignore any commits with a newer timestamp.
Setting an oldest timestamp in Connection.set_timestamp indicates that future read timestamps will be at least as recent as the oldest timestamp, so WiredTiger can discard history before the specified point. It is critical that the oldest timestamp update frequently or the cache can become full of updates, reducing performance.
Setting a stable timestamp in Connection.set_timestamp indicates a known stable location that is sufficient for durability. During a checkpoint the state of a table will be saved only as of the stable timestamp. Newer updates after that stable timestamp will not be included in the checkpoint. That can be overridden in the call to Session.checkpoint. It is expected that the stable timestamp is updated frequently. Setting a stable location provides the ability, if needed, to rollback to this location by placing a call to Connection.rollback_to_stable. With the rollback, however, WiredTiger does not automatically reset the maximum commit timestamp it is tracking. The application should explicitly do so by setting a commit timestamp in Connection.set_timestamp.
Commit timestamps cannot be set in the past of any read timestamp that has been used. This is enforced by assertions in diagnostic builds, if applications violate this rule, data consistency can be violated.
The commits to a particular data item must be performed in timestamp order. Again, this is only checked in diagnostic builds and if applications violate this rule, data consistency can be violated.
The extension API, used by modules that extend WiredTiger via Connection.get_extension_api, is not timestamp-aware. In particular, WT_EXTENSION_API::transaction_oldest and WT_EXTENSION_API::transaction_visible do not take timestamps into account. Extensions relying on these functions may not work correctly with timestamps.