Data Integrity

Transactions

There are two major aspects to transaction processing: atomicity and automatic recovery. These are related yet different aspects of transaction processing, and not all products supply both. FairCom DB API .NET provides a set of functions and file modes that cover both aspects of transaction processing.

 

Atomicity

Often, when updating a table, you perform several functions in a group. For instance, when creating an invoice, you update several tables: the account balance in the customer file, the invoice file, an invoice detail file, inventory records, and others. It is important that all of these actions take place to keep the files synchronized. If some of the actions take place, but not all, your files may be out of sync, and it can be difficult to correct the problem later. If one action cannot take place, it would be best to not let any take place. We call this atomicity. The FairCom DB API .NET API provides functions that provide this feature. You can mark a set of operations so that none will take place unless they can all take place. The API goes beyond this, allowing you to create "savepoints" where you can partially back out a group of operations, and "roll back" transactions to a given point, so that you can restore your data back to a state that it was in sometime in the past.

 

Automatic Recovery

Once you establish full transaction processing by creating tables using the CREATE_MODE.TRNLOG_CREATE mode, you can take advantage of the automatic recovery feature. Atomicity will generally prevent problems of files being partially updated. However, there are still situations where a system crash can cause data to be lost. Once you have signaled the end of a transaction, there is still a "window of vulnerability" while the application is actually committing the transaction updates to disk. In addition, for speed considerations some systems buffer the data files and indexes so that updates may not be flushed to disk immediately. If the system crashes, and one of these problems exists, the recovery logic detects it. If you set up the system for automatic file recovery, the recovery logic automatically resets the table back to the last, most complete, state that it can. If any transaction sets have not been completed, or "committed", they will not affect the table.

 

Creating Tables for Transaction Processing

Only tables created with the modes CREATE_MODE.PREIMG_CREATE and CREATE_MODE.TRNLOG_CREATE will participate in a transaction.

Tables created with CREATE_MODE.PREIMG_CREATE mode will participate in a transaction and only transaction atomicity is applied to them.

Tables created with CREATE_MODE.TRNLOG_CREATE have all the attributes of CREATE_MODE.PREIMG_CREATE but will also generate the transaction logs necessary for automatic recovery.

 

Starting a Transaction

The Begin() method of classes CTSession, CTDatabase, CTTable and CTRecord starts a new transaction. Choose logical groups of file updates to be delimited as transactions.

Record locks are held on updated records for the duration of the transaction, so you don't want to make the transaction group too large or it will consume the system resources. On the other hand, you may not want to make the transaction group too small or the effect of grouping actions is lost.

Using our example from above, you don't want to have the transaction group involve more than one invoice. You also don't want it to involve less than a whole invoice.

// start a new transaction
try
{
   ARecord.Begin()
}
catch (CTException err)
{
   Console.Write("Begin transaction failed with error {0}\n", err.GetErrorCode());
}

 

Terminating a Transaction

When all update operations have been completed, terminate a transaction by calling the Commit() method to commit all changes.


// Start transaction
ARecord.Begin();

// write the record
try
{
   ARecord.Write();
   ARecord.Commit();
}
catch (CTException err)
{
   ARecord.Abort();
   Console.Write("Commit transaction failed with error {0}\n", err.GetErrorCode());
}

Call Abort() to terminate the transaction and abort all changes since the start of the transaction.

 

Save Points

There are times when you want to abort only a portion of a transaction. You may be processing several optional paths of a program, going down one branch, then backing out and trying another branch. It may be possible that you don't want any of the updates to occur until you are completely finished, but you want the flexibility to back out part of the updates. Another possibility would be if you have run into some form of update error, such as an add record failing due to a duplicate key. You would want to back up to a prior point, correct the problem, and continue. The c-tree Servers let you implement this by using savepoints.

A savepoint is a temporary spot in the transaction that you may want to roll back to without having to abort the entire transaction. During a transaction, when you want to put a placeholder in the process, call the SetSavePoint() method. This does not commit any of the updates. The function returns a savepoint number, which you should keep track of. You can make as many ...SavePoint() calls as you wish during a transaction, and each time you will be given a unique savepoint number.

When you decide that you want to roll back to a savepoint previously saved by a call to SetSavePoint(), issue a RestoreSavePoint() call. You should pass to RestoreSavePoint() the savepoint number that you saved. This returns your data to the state it was at the point you issued the specified SetSavePoint() call, without aborting the entire transaction.

 

Locking

The most significant difference between coding applications for single-user environments and multi-user environments (including local area networks) has to do with performing record updates (rewrites) and record deletions.

The basic problem is that to perform a record update or delete in a multi-user system, you must own a record lock on the record of interest. Locks should be acquired when the user reads the record in preparation for updates, and should not relinquish the lock until the update is completed. However, one must be careful to help ensure that locks are held for the shortest time possible or the performance of the application will be adversely affected.

Two types of locks can be applied to a record:

  • A READ lock (also called a "shared" lock) on a record allows an unlimited number of other READ locks on that record (from other threads, etc), but prevents WRITE locks.
  • A WRITE lock (also called an "exclusive" lock) prevents any other locks (of either type) on that record.

For a full list of lock modes, see Lock Modes.

 

Starting Locks

Start acquiring locks by passing the appropriate lock mode to the Lock method inherited by CTSession, CTDatabase, CTTable, and CTRecord from CTBase.

// start locking
ARecord.Lock(LOCK_MODE.WRITE_BLOCK_LOCK);

After a successful call to Lock, the FairCom DB API .NET API locks all records as they are read using the lock mode passed to Lock. Suspend record locking temporarily by calling the Lock function with the mode LOCK_MODE.SUSPEND_LOCK. Suspending locks does not release any locks, but while locks are suspended, no record reads are automatically locked.

// suspend locking
ARecord.Lock(LOCK_MODE.SUSPEND_LOCK);

Suspended locking can be resumed by calling Lock with a restore lock mode: LOCK_MODE.RESTORE_READ_LOCK, LOCK_MODE.RESTORE_READ_BLOCK_LOCK, LOCK_MODE.RESTORE_WRITE_LOCK, or LOCK_MODE.RESTORE_WRITE_BLOCK_LOCK.

// resume locking
try
{
   ARecord.Lock(LOCK_MODE.RESTORE_WRITE_BLOCK_LOCK);
}
catch (CTException err)
{
   Console.Write("Resume lock failed with error {0}\n", err.GetErrorCode());
}
 

Lock Modes

Use the following lock modes, defined in LOCK_MODE enum, when calling Lock():

Lock Mode Explanation
FREE_LOCK Free all locks.
READ_LOCK Non-blocking read lock. If the lock cannot be acquired an error is returned.
READ_BLOCK_LOCK Blocking read lock. The thread will block until the lock can be acquired.
WRITE_LOCK Non-blocking write lock. If the lock cannot be acquired an error is returned.
WRITE_BLOCK_LOCK Blocking write lock. The thread will block until the lock can be acquired.
RESET_LOCK Equivalent to calling Lock with FREE_LOCK followed by Lock with WRITE_LOCK.
SUSPEND_LOCK Temporarily suspend locking.
RESTORE_READ_LOCK Restore READ_LOCK mode after SUSPEND_LOCK.
RESTORE_READ_BLOCK_LOCK Restore READ_BLOCK_LOCK mode after SUSPEND_LOCK.
RESTORE_WRITE_LOCK Restore WRITE_LOCK mode after SUSPEND_LOCK
RESTORE_WRITE_BLOCK_LOCK Restore WRITE_BLOCK_LOCK mode after SUSPEND_LOCK.


Freeing Locks

Locks are freed by calling Unlock or by calling Lock with the LOCK_MODE.LOCK_FREE_LOCK mode. If freeing locks inside an active transaction, the locks of updated records will only be actually freed when the transaction terminates.

// free locks
ARecord.Unlock();

 

Freeing Locks Associated with a Table

You can free only the locks associated with a table by calling the CTTable.UnlockTable() method. Only the locks held for records of the table are released, and all other record locks are kept.

// free locks for a table
try
{
   ATable.UnlockTable();
}
catch (CTException err)
{
   Console.Write("Unlock table failed with error {0}\n", err.GetErrorCode());
}

If freeing locks associated with a table inside an active transaction, the locks of updated records will only be actually freed when the transaction terminates.