Transaction options explained

By Claudio Valderrama - Copyright www.cvalde.net

A transaction is a logical unit of work. This means several commands may be comprised in a single transaction. Also, a single command may encompass several units of physical work, for example, an UPDATE with a WHERE clause that yields more than one affected record. When you save (commit) or undo (rollback) a transaction, this affects all actions performed under this transaction (since the transaction was started or the last commit/rollback that retained the transaction context). While the transaction is in progress, changes inside it cannot be viewed by another transaction (except where in dirty read mode, see below). A transaction has no theoretical limit in the number of commands it can undo or save; however, in practice, there are practical limits imposed by the architecture of each RDBMS.

It's important to realize that all Firebird/InterBase® work is done inside a transaction, either implicit or explicit. This is by design because it allows the multi-versioning engine to work. If you aren't controlling transactions, each statement is executed in a transaction that terminates after that statement automatically. This is known as auto commit. Take note that even in this case, each implicit transaction may span several operations, because changing only one record may fire a trigger and it may modify several records in another table and so on. Therefore, auto commit means one statement and all its effects are treated as one transaction that is committed when successful or it's rolled back when any operation fails.

Firebird/InterBase® doesn't support nested transactions. Hence, the stored procedure and trigger language doesn't support transaction control commands. In Firebird/InterBase®, each procedure or trigger runs in the context of the transaction initiated by the client application. However, Firebird/InterBase® allows several concurrent or parallel transactions in the same connection but sadly this capability is not offered through generic database access products like the BDE or ODBC: for this, you have to rely on the Firebird/InterBase®-API, on the free FIB, on the new IBX or in the third party, full source commercial product known as IBO. For people used to relational databases, the options offered by Firebird/InterBase® may seem cryptic, but this is more due to the different names than to completely different possibilities. In fact, Firebird/InterBase® performs like any other relational database in the general cases. This is the general syntax:

 SET TRANSACTION [NAME transaction] 
 [READ WRITE | READ ONLY] 
 [WAIT | NO WAIT] 
 [[ISOLATION LEVEL] {SNAPSHOT [TABLE STABILITY] 
 | READ COMMITTED [[NO] RECORD_VERSION]}] 
 [RESERVING <reserving_clause> 
 | USING dbhandle [, dbhandle ...]]; 
 <reserving_clause> = table [, table ...] 
 [FOR [SHARED | PROTECTED] {READ | WRITE}] [, <reserving_clause>] 

back to top of page

Access mode

This is fairly obvious. A read write transaction, the default mode, can retrieve data and alter the contents of the database, provided that the user that starts the transaction has been given RW permissions (rights) over the affected objects. A read-only transaction cannot execute commands that alter contents of the database; it only can retrieve data, provided that the user that starts the transaction has been given RO permissions over the affected objects. Note that RW means insert/update/delete and RO means select/reference in this context.

back to top of page

Blocking mode

With WAIT, the default mode, when a conflict appears, the user that finds a conflict with a previous operation of another uncommitted transaction will be kept in a suspended state until that operation can proceed. The control is not returned to the client until the operation proceeds. With NO WAIT, the user that produces a conflict with a previous operation of another uncommitted transaction will see an error message immediately. Take note a conflict not only may happen while writing but while reading as well. For reading behavior, see below Read committed under isolation mode. For writing behavior, the response doesn't depend on the isolation mode:

back to top of page

Isolation mode

Also known as isolation level, it controls the visibility of one transaction with respect to the changes made by other concurrent transactions. People that come from desktop databases and without acquaintance with transactions and relational concepts, often find problems understanding why they must care about this feature. There are 4 known levels:

To understand how Read Access and Isolation Level interacts, I've decomposed a matrix of possibilities into a list of possibilities:

See Blocking mode above to understand the type of messages that appear when such conflicts happen in WRITE mode.

back to top of page

Pre-allocation mode

Although SET TRANSACTION is available from SQL, DSQL and isql, this mode seems to be used mostly by embedded SQL applications. It's called table reservation in the official documentation. It allows finer granularity over the requested resources (in this case, tables) when the transaction starts instead of waiting the transaction to attempt its operations on table. This technique reduces the possibility for deadlocks. I will skip the USING form because it's available only to embedded SQL (it's not for Dynamic SQL) and specifies handles to complete databases, so it limits from the start the number of databases accessed by a transaction. Regarding the RESERVING form, it's followed by a comma-separated list of tables and then the FOR reserved word and these sharing options:

and these access mode options:

so you have 4 possible combinations. After them, you can put a comma followed by a new comma-separated list of tables followed by FOR and their desired pre-allocation mode and so on. Let's try to address the four combinations:

A good way of trying different combinations and watching what happens is to launch two instances of the command-line utility isql.exe and experimenting. It supports the complete syntax shown at beginning (with the exception of USING because it doesn't work in Dynamic SQL as it was explained previously) so you can play with waiting, conflicts, deadlocks and error messages.

It's worth to say that probably you don't see the behavior described in the default parameters when connected through the BDE. Probably, the BDE's InterBase® driver uses no wait and read committed to behave as with other engines, for compatibility reasons.

Also, it can't be overstated to emphasize that generators fall outside transactions. They have a unique value for the database at one instant of time regardless of the isolation level of the transactions using them. Also, the changes made to a generator value by means of gen_id aren't affected by either commit or rollback, so gen_id is really an atomic global call, because not only it returns the new generator value to all transactions but its invocations are serialized, to guarantee uniqueness of such generator value when used to increment or decrement the current value.

The following table lists the equivalences of the names used for different isolation levels:

API level constants & Firebird/InterBase®O Language level & tools BDE level
(Not supported) (Not supported) Dirty Read
Read Committed Read Committed Read Committed
Concurrency Snapshot Repeatable Read
Consistency Snapshot table stability (Not supported)