A Transaction is an atomic, consistent, isolated and durable (“ACID”) unit of work. Transactions guarantee that all database operations of this Transactions are either all written to the database in one atomic “commit” or cancelled and undone in one atomic “abort”. To read more about Transactions, Wikipedia provides a good entry point: http://en.wikipedia.org/wiki/Database_transactions. The following chapter describes the state of Transaction support in upscaledb, and gives a primer how to use them.
upscaledb allows an unlimited number of Transactions to be created
in parallel. The Transactions do not block each other in a traditional
sense (i.e. they do not block access to the database pages). However, if
a Transaction tries to read or modify a key that is currently modified
in another active Transaction then the error UPS_TXN_CONFLICT
is
returned. upscaledb implements the “read-committed” isolation level.
Transaction support has to be explicitly enabled with the flag
UPS_ENABLE_TRANSACTIONS
when creating or opening an Environment or a
Database. The support for Transactions comes with a certain cost
regarding performance, because it implicitly enables logging/recovery.
The following code enables the use of Transactions:
st = ups_env_create (&env, “test.db”, UPS_ENABLE_TRANSACTIONS, 0644, 0);
if (st != UPS_SUCCESS) {
printf ("error %d (%s)\n", st, ups_strerror (st));
exit (–1);
}
A new Transaction can be started with ups_txn_begin
. The parameter
reserved
is unused and should be set to NULL. The flags are also
unused and should be set to 0. The name
is optional and can later be
retrieved with ups_txn_get_name
.
ups_status_t
ups_txn_begin (ups_txn_t **txn, ups_env_t *env, const char *name,
void *reserved, uint32_t flags);
API reference for ups_txn_begin
If Transactions were not enabled (with UPS_ENABLE_TRANSACTIONS
), the
function will return UPS_INV_PARAMETER
.
A Transaction can be used with the following functions:
In all four cases, the second parameter is the Transaction handle. The
following example illustrates how to insert an item with a transactional
ups_db_insert
; the use of the other three functions is very similar.
The changes to the Database are only written if the Transaction is
committed! If it is aborted or the application crashes, the changes are
not written.
ups_txn_t *txn;
if ((st = ups_txn_begin (&txn, db, 0))) {
printf (“ups_txn_begin failed: %d (%s)\n”, st, ups_strerror (st));
exit (–1);
}
st = ups_db_insert (db, txn, key, record, 0);
if (st != UPS_SUCCESS) {
printf (“ups_db_insert failed: %d (%s)\n”, st, ups_strerror (st));
exit (–1);
}
In case of ups_cursor_create
, the Cursor will be “attached” to the
Transaction during its whole lifetime. You have to close the Cursor
before committing or aborting the Transaction, otherwise the commit or
abort fails with an error.
When committing a Transaction, it is written atomically to the Database. The commit is fairly easy:
ups_status_t
ups_txn_commit (ups_txn_t *txn, uint32_t flags);
API reference for ups_txn_commit
Again, the flags are not yet used; set them to 0.
st = ups_txn_commit (txn, 0);
if (st != UPS_SUCCESS) {
printf (“ups_txn_commit failed: %d (%s)\n”, st, ups_strerror (st));
exit (–1);
}
As mentioned above, the commit will fail with UPS_CURSOR_STILL_OPEN
if
there are still Cursors attached to this Transaction.
When aborting a Transaction, all changes are undone. Analog to the commit, aborting a Transaction is simple:
ups_status_t
ups_txn_abort (ups_txn_t *txn, uint32_t flags);
API reference for ups_txn_abort
Again, the flags are not yet used; set them to 0.
st = ups_txn_abort (txn, 0);
if (st != UPS_SUCCESS) {
printf (“ups_txn_commit failed: %d (%s)\n”, st, ups_strerror(st));
exit (–1);
}
As mentioned above, the abort will fail with UPS_CURSOR_STILL_OPEN
if
there are still Cursors attached to this Transaction.