User Transactions
Transactions reduce the possibility of lost data. If you have a number of modifications to make to a file and you must be sure that either all or none of those modifications are made, include the operations for making those modifications in a transaction. By defining explicit transactions, you can force the MicroKernel to treat multiple Btrieve operations as an atomic unit. To include a group of operations within a transaction, you enclose those operations between a Begin Transaction operation (19) and an End Transaction operation (20).
The MicroKernel provides two types of transactions: exclusive and concurrent. The type you use depends on how severely you want to restrict other clients’ access to the file you are modifying. (The MicroKernel does not allow other applications or clients to see the changes involved in any transaction (exclusive or concurrent) until the transaction ends.)
When a task operates on a file inside an exclusive transaction, the MicroKernel locks the entire file for the duration of the transaction. Once a file is locked in an exclusive transaction, other non-transactional clients can read the file, but they cannot make changes to it. Another client that is also in an exclusive transaction cannot perform any operations that require a position block on the file (even standard Get or Step operations), until the first client unlocks the file by finishing the transaction.
When an application operates on a file inside a concurrent transaction, the MicroKernel locks only the affected records and pages in the file, as follows:
As with exclusive transactions, other tasks can always read data that is locked from within a concurrent transaction. In any data file, multiple tasks can have their own concurrent transactions operating, in which they are performing Insert, Update, or Delete operations, or in which they are performing Get or Step operations that contain read lock biases. The only restriction on these activities is that no two tasks can lock the same record or page simultaneously from their respective concurrent transactions.
The following additional features apply to concurrent transactions:
If the transaction simply reads a record, the MicroKernel does not lock either the record or the corresponding page.
Locks
Records, pages, or even an entire file can be locked. Once locked, a record, page, or file cannot be modified by any client other than the one responsible for the lock. Similarly, locks owned by one client can prevent record, page, or file locking by another client, as explained in the rest of this section.
The MicroKernel provides two kinds of locks: explicit and implicit. When a client specifically requests the lock by including the lock request with a Btrieve operation code, that lock is called an explicit lock. However, even when a client does not explicitly request a lock, the MicroKernel may lock an affected record or page as the result of an action that the client performs. In this situation, the lock that the MicroKernel makes is called an implicit lock (see Implicit Record Locks and Implicit Locks).
*Note: Unless otherwise noted, the term record lock refers to an explicit record lock.
Records can be locked implicitly or explicitly. Pages can be locked only implicitly. Files can be locked only explicitly.
The rest of this section discusses the various locks as they apply in both non-transactional and transactional environments.
Explicit Record Locks in a Non-Transactional Environment
This section discusses explicit record locks in a non-transactional environment. For information about how transactions affect the use of record locks, refer to Record Locks in Concurrent Transactions.
A client might not want to rely on passive concurrency. However, that same client might need to ensure that any record it reads can later be updated or deleted without receiving Status Code 80 (which would require the client to reread the record). A client can comply with both these requirements by requesting an explicit record lock on the record. For your application to lock a record when reading it, you can add one of the following bias values to the appropriate Btrieve Get or Step operation code:
You can only apply these lock biases to Get and Step operations. You cannot specify a lock bias on any other operations in a non-transactional environment.
*Note: Single-record locks and multiple-record locks are incompatible; therefore, a client cannot hold both types of locks simultaneously on the same position block (or cursor) in a file.
Single-Record Locks
A single-record lock allows a client to lock only one record at a time. When a client successfully locks a record with a single-record lock, that lock remains in effect until the client completes one of the following events:
When a client locks a record, no other client can perform any Update (3) or Delete (4) operations on that record. However, other clients can still read the record using a Get or Step operation, as long as the Get or Step operation adheres to the following conditions:
Multiple-Record Locks
A multiple-record lock allows a client to lock several records concurrently in the same file. When a client successfully locks one or more records with multiple-record locks, those locks remain in effect until the client completes one or more of the following events:
*Note: An Update operation does not release a multiple-record lock.
As with a single-record lock, when a client locks one or more records using a multiple-record lock, no other client can perform any Update (3) or Delete (4) operations on those records. Other clients can still read any of the locked records using a Get or Step operation, as described in Locks.
When a Record Has Already Been Locked
When your client requests a no-wait lock on a record that is currently not available (either the record is locked by another client or the whole file is locked by an exclusive transaction), the MicroKernel returns either Status Code 84 (Record/Page Locked) or Status Code 85 (File Locked). When your client requests a wait lock and the record is currently not available, the MicroKernel retries the operation.
Record Locks in Concurrent Transactions
Because exclusive transactions (operation 19) lock the entire file, record locks in a transaction apply only to concurrent transactions (operation 1019). (For information about transaction types, see User Transactions).
The MicroKernel allows a client to lock either single or multiple records in a file from within a concurrent transaction. The client can lock the record(s) by either of the following methods:
When you specify a record lock bias value on a Begin Concurrent Transaction operation, each operation inside that transaction—if it has no bias value of its own—inherits its bias value from the Begin Concurrent Transaction operation. For example, a Get Next operation (06) inherits the 200 bias from the preceding biased Begin Concurrent Transaction operation (1219), causing the Get Next to be performed as a no-wait read and lock operation (206).
As implied in the preceding paragraph, a client can still add bias values to the individual Step or Get operations that occur within the concurrent transaction. Biases added in this manner take precedence over the inherited bias.
The events that cause the release of single- and multiple-record locks in concurrent transactions are similar to those for the non-transactional environment. For single, see Single-Record Locks. For multiple, see Multiple-Record Locks with the following exceptions:
A Close operation does not release explicit record locks secured from within a concurrent transaction. With version 7.0 of the MicroKernel, you can close the file within a transaction even if a record is locked.
Finally, when a client in a concurrent transaction reads one or more records using an unbiased Get or Step operation, and no lock bias was specified on the Begin Concurrent Transaction operation, the MicroKernel performs no locking.
Implicit Record Locks
When a client attempts to update or delete a record, either external to any transaction or from within a concurrent transaction, the MicroKernel implicitly tries to lock that record on behalf of the client. In an exclusive transaction, an implicit record lock is unnecessary because the MicroKernel locks the entire file prior to performing the Update or Delete operation. (See File Locks).
The MicroKernel can grant an implicit record lock to a client as long as no other client:
*Note: The MicroKernel allows any single client to hold both an explicit lock and an implicit lock on the same record.
The MicroKernel performs the specified Update or Delete operation only if it can successfully obtain the implicit record lock and any other locks required to secure the integrity of the file during execution of the operation. (See Implicit Locks).
If the operation is in a non-transactional environment, the MicroKernel drops the implicit record lock on completion of the Update or Delete operation. If the operation is in a concurrent transaction, the MicroKernel retains the lock. The lock then remains in effect until the client ends or aborts the transaction, or the client is reset (which implies an Abort Transaction operation). No explicit Unlock operation is available to release implicit record locks.
By retaining implicit locks during a transaction, the MicroKernel can prevent conflicts that occur as the result of a client explicitly locking a record (via a Get/Step operation with a lock bias value) if that record has a new uncommitted image that another client generates.
Consider what could happen if the MicroKernel did not retain implicit locks. Client 1, from within a concurrent transaction, performs an update on record A, thereby altering the image of the record. However, because client 1 has not ended its concurrent transaction, it has not committed the new image. Client 2 attempts to read and lock record A.
With no implicit locks retained, client 1 no longer has an implicit record lock on record A, meaning that client 2 can successfully read and lock the record. However, client 2 reads the old image of record A, because client 1 has not committed the new image. When client 1 ends its transaction (committing the changed image of record A) and client 2 attempts to update record A, the MicroKernel returns Status Code 80 (Conflict) because client 2’s image of that record is no longer valid. (See the example in Table 38.)
Consider a situation in which a client has locked a record (either explicitly or implicitly) or has locked the entire file containing that record. If another client attempts to update or delete the record in question from within a concurrent transaction—if it tries to implicitly lock the record—some implementations of the MicroKernel will wait, continually retrying the operation until the client whose lock is blocking the operation releases that lock. (No version of the MicroKernel attempts any retry effort for a non-transactional Update or Delete.)
Supplying a bias value of 500 on the Begin Concurrent Transaction operation (1519) forces the MicroKernel not to retry the Insert, Update, and Delete operations within a transaction.
For local clients, the MicroKernel performs deadlock detection. However, because the 500 bias suppresses retries, the MicroKernel does not need to perform deadlock detection.
This 500 bias value on the Begin Transaction operation can be combined with the record lock bias values. For example, using 1019 + 500 + 200 (1719) suppresses retries for Insert, Update, and Delete operations and specifies single no-wait read locks at the same time.
The following example illustrates the usefulness of implicit locks. In the example, temporarily assume that implicit locks do not exist.
1. Begin concurrent transaction.
2. Read record A.
3. Update record A (locks on pages involved, but no implicit lock on record).
4. Read record A with single-record lock (explicit lock on record).
5. End transaction (releases page locks).
6. Update record A (conflict, Status Code 80).
7. Reread record A with lock.
8. Update record A.
Assuming that the MicroKernel does not apply an implicit record lock in Step 3, client 2 can successfully read and lock record A in Step 4 but cannot update that record in Step 6 because in Step 4, client 2 reads a valid image of record A. However, by the time client 2 reaches Step 6, that image is no longer valid. In Step 5, client 1 commits a new image of record A, thereby invalidating the image of the record read by client 2 in Step 4.
In reality, however, the MicroKernel implicitly locks record A in Step 3, which means that the MicroKernel returns Status Code 84 in Step 4, requiring client 2 to retry its read operation until client 1 performs Step 5.
Consider what would happen if Steps 3 and 4 were reversed in the preceding example. Client 2 obtains an explicit lock on record A. Client 1 is forced to wait and retry its Update operation until client 2 completes its own update of record A (which releases client 2’s explicit lock on that record). On client 1’s next retry to update record A, the MicroKernel returns Status Code 80. This status indicates that client 1’s image of record A was no longer valid (client 1 having read record A prior to that record being changed by client 2).
Implicit Locks
Clients have significant freedom to modify a file simultaneously because they share cache under the same MicroKernel. Non-transactional modifications (insert, update, or delete operations) never block other non-transactional modifications or modifications in concurrent transactions by another client. Pending modifications in a concurrent transaction do not block other modifications (either non-transactional or in concurrent transactions), as long as those changes do not affect the same records.
The MicroKernel tries, on behalf of the client, to implicitly lock the records that are modified during execution of an Insert, Update, or Delete operation if the modification occurs either outside of a transaction or from within a concurrent transaction. (In an exclusive transaction, an implicit record or page lock is unnecessary because the MicroKernel locks the entire file prior to performing an Update or Delete operation. In the case of an Insert operation, the MicroKernel requests a file lock if the client does not have one yet. See File Locks) As with implicit record locks, the MKDE provides implicit page locks; the client does not explicitly request them.
The records on a data page being modified (or inserted) must always be locked. However, a single operation might need to lock several other records as well. For example, if the change made to a record involves one or more of the record’s keys, then the MicroKernel must lock the records on the index pages containing the affected key values. The MicroKernel must also lock all index pages modified by the action of balancing the B-tree(s) during operation. If a modification affects the variable-length portion of a record, the MicroKernel must lock the entire variable page(s) as well.
If such an operation is performed in a non-transactional environment, the MicroKernel drops the implicit record locks on completion of the operation. If the operation is performed from within a concurrent transaction, the MicroKernel retains the locks, which then remain in effect until the client ends or aborts the transaction, or until the client is reset (which implies an Abort Transaction operation). No explicit Unlock operation is available to release implicit record or page locks.
If an Insert, Update, or Delete operation issued in a concurrent transaction must modify a record or page (it requires an implicit record lock), but that record or page is currently locked by another concurrent transaction (or the whole file is locked by an exclusive transaction), the MicroKernel waits, continually retrying the operation until the client whose lock is blocking the operation releases that lock. The MicroKernel does not attempt any retry effort for a nontransactional Update or Delete.
When a client is unsuccessful in getting an implicit record lock, the client can suppress retries on the operation by using bias value 500 on the Begin Concurrent Transaction operation.
Implicit page locks and explicit or implicit record locks have no blocking effect on each other. A client can read and lock a record on a page even if another client has implicitly locked the page containing that record (as long as the record to be locked is not the same one that has been updated, as discussed in Implicit Record Locks). Conversely, a client can update or delete a record, thereby implicitly locking the data page that contains the affected record even if that data page contains a record already locked by another client.
File Locks
When a client touches a file for the first time in an exclusive transaction, that client tries to obtain a file lock.
*Note: As the preceding statement implies, the MicroKernel does not lock a file when the client performs a Begin Transaction operation. The lock occurs only when the client reads or modifies a record after performing the Begin Transaction operation.
A file lock, as its name suggests, locks the entire file. A client’s file locks remain in effect until that client ends or aborts the transaction, or until the client is reset (which implies performing an Abort Transaction operation).
If a client tries to lock a file in an exclusive transaction but another transaction already holds a lock on that file (a record, page, or file lock), the MicroKernel waits, continually retrying the operation until the client whose lock is blocking the operation releases that lock. In addition, if a local client blocks the operation and the MicroKernel detects a deadlock situation, the MicroKernel returns Status Code 78 (Deadlock Detected).
When a client is unsuccessful in getting a file lock, the client can suppress retries on the operation using a no-wait lock bias value of 200 or 400 on the Begin Exclusive Transaction operation (219 or 419). If a client starts a transaction in this way, the MicroKernel returns Status Code 84 or 85 when a file lock cannot be granted.
Bias values 200 and 400 are derived historically from record locks. However, the concept of single and multiple locks from the record lock environment means nothing in an exclusive transaction environment. In effect, all records in the file are locked when the file is locked. Only the no-wait meaning of the biases is preserved in the exclusive transaction environment.
The MicroKernel accepts a wait lock bias (100 or 300) on a Begin Exclusive Transaction operation (operation 119 or 319, respectively); however, these additional bias values have no meaning because the default mode on the Begin Transaction operation is to wait.
When any part of a file is first touched in an exclusive transaction, the MicroKernel locks the entire file. Therefore, the MicroKernel ignores record lock bias values explicitly added to the operation codes for any Get or Step operations performed inside an exclusive transaction, with the following exception.
When a client performs a Begin Transaction operation in wait mode (operation 19, 119, or 319), but the first read (Get or Step operation) in that transaction is biased by 200 or 400 (a no-wait lock bias), the no-wait bias takes precedence over the Begin Transaction operation’s wait mode. Therefore, when the client performs this biased read operation but cannot lock the file (for example, another client has already locked a record in the file), the MicroKernel does not wait (which is its default) and does not check for deadlock, because it assumes that the client retries the read operation an unlimited number of times. In this same situation, other versions of the MicroKernel that perform retries automatically recognize the no-wait bias as an indication not to retry the file lock and not to check for deadlock.
*Note: The 200 and 400 bias values on a Get or Step operation performed from within an exclusive transaction have only the meaning of not waiting; they do not request an explicit record lock, as they would from within a concurrent transaction.
File locks are incompatible with both record locks and page locks; therefore, the MicroKernel does not grant a file lock to a client if another client holds a record or a page lock on that file. Conversely, the MicroKernel does not grant a record or page lock to a client if another client has already locked that file.