Quince's transaction
class
provides an RAII
wrapper for SQL's BEGIN
, COMMIT
and
ROLLBACK
commands.
You can construct a transaction
object while another transaction
object, for the same database [19] , exists on the same thread. Then the newer transaction
is said to be nested. Nested transactions (to any depth)
wrap SQL's SAVEPOINT
, ROLLBACK TO
,
and RELEASE SAVEPOINT
commands.
Every DBMS offers a range of options about its synchronization behaviour
(isolation level, “shared-cache locking”). To the extent that
that control is available to quince applications, it is via the backend
libraries, not via quince
itself. Quince's transaction
class assumes that the synchronization behaviour has already been configured
to your taste, or more accurately: it has no awareness of it. If the DBMS
does the right thing, or the wrong thing, or is efficient, or inefficient,
then that is what an application using the transaction
class will see.
Code for a typical transaction looks like this:
{ transaction txn(db); // ... statements to be executed in one transaction ... txn.commit(); }
If the body runs all the way through, then txn.commit();
will be executed and the transaction will
be committed. If txn
falls
out of scope without being committed, then the transaction will be rolled
back. So if you are part way through a transaction and you want it rolled
back, throw an exception that will not be caught within the transaction
's scope.
Caution: the transaction will be rolled back if you bypass the commit()
by any means, including break
,
continue
or return
.
That can lead to bugs, e.g. you've finished the work you wanted to do, you
think you're return
ing in triumph,
and then it all gets rolled back as if it were failure. To avoid confusion,
I suggest that you never use break
,
continue
or return
in a way that would exit a transaction
's
scope.
commit()
is only allowed on the transaction
that is currently the innermost one for its database. If you try to commit
an outer transaction
(not
necessarily the outermost) while an inner transaction
exists, or commit a transaction
that was created on another thread, then commit()
throws a non_current_txn_exception
.
When an operation would result in deadlock, the DBMS reports it to quince,
and quince throws deadlock_exception
.
Usually that is not an error, but it is a sign that the transaction needs
to be rolled back and tried again. Here is some code you can use:
for (;;) { try { transaction txn(db); // statements to be executed in one transaction ... txn.commit(); } catch (const deadlock_exception &) { continue; } break; }
Quince transaction
s pass
on the subtle behaviour of the SQL constructs that they wrap, so you might
need to know what is going on at the SQL level. The following table provides
translations.
Quince |
SQL for an outermost transaction |
SQL for a nested transaction |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
(nnn
stands for a number that quince allocates,
to identify each nested transaction uniquely within an outermost transaction.)
[19] Transactions for different databases are unrelated to each other, and do not count as nested within each other.