SQL database system
 
Manual page for record(locking)

Record locking

shsql has a built-in facility for record locking. shsql record locking is an advisory facility that allows database records to be reserved while changes are pending. It prevents other users from reserving or modifying records to which changes are pending. Record locking is recommended for any table that could be updated by more than one user or process concurrently. If used properly, record locking can alleviate some problems that are addressed by transaction control in full-blown DBMS systems.

Successful use of record locking requires database accesses to be coded to detect attempts to access/update records that someone else has already locked. It is best if all programs that update a table that uses record locks be coded using the same conventions. Some procedural examples are given below. Developers should understand how record locking interacts with table locking, and be familiar with strategies for avoiding deadlock.

shsql's record locking facility is intended to be convenient and easy to work with, with special consideration for web-based applications. The SELECT .. FOR UPDATE command is used to lock records. Locked records are automatically unlocked upon the next UPDATE or DELETE executed by the same user/identity who locked the record. Record locks will also automatically expire (time out) after a certain amount of time (30 minutes by default, configurable). This is useful in web-based applications where users can lock records for editing but then aren't forced to follow through and save or cancel.


Here are the rules for using shsql record locking:

  • Tables for which record locking is to be used must have _locktime and _lockowner fields. These fields (using these exact names) may be specified when the table is CREATEed or by using ALTER, and may be at any logical location within the record.


  • Any process that will lock a record or modify a locked record must have an identity associated with it.


  • In order to lock record(s), the SELECT..FOR UPDATE command is used.
    	select * from tablename where condition FOR UPDATE
    
    The asterisk (*) is required, and the command must be a simple, single-table SELECT command (JOIN, GROUP BY, INTO, LIMIT etc. cannot be used). All eligible records matching the condition will be locked (a limit of 200 records applies).


  • The record lock will be released automatically upon the next UPDATE or DELETE that affects the record, executed by the identity holding the lock. No special syntax is required on the UPDATE or DELETE commands.


  • Once record locking is set up for a table it is best for all processes to use record locking when accessing it. However, nothing prevents an unlocked record from being modified by UPDATE or DELETE command directly, without locking the record first.


  • SELECT .. FOR UPDATE is used to lock one record or multiple records. If feasible, applications should be designed so that single record locks accomplish necessary protection, since locking multiple records can add complexity.


  • If someone has locked a record, then single record UPDATE, DELETE, and SELECT..FOR UPDATE commands issued by someone else on that record will not be successful while the lock is in effect. An error code# 7 will be returned by the API, with no error message generated, since this is a "normal" occurance.


  • SELECT .. FOR UPDATE commands that attempt to lock multiple records will receive an error code# 7 only when fetching a record that is already locked by someone else. This allows applications to ensure that they can reserve all necessary records before modifying them. NOTE: A multi-row SELECT .. FOR UPDATE causes a table write lock to be in effect until all rows are fetched, so all rows should be retrieved without delay, even if some of them involve an error.


  • Suppose the following occurs: 1) a certain identity acquires a lock; 2) then the lock expires (times out); 3) the same identity issues an UPDATE or DELETE. The operation will be allowed unless some other identity has locked (and perhaps unlocked) the record in the meantime.


  • Subsequent SELECT..FOR UPDATE commands issued by the lock owner on a locked record will reset the expiration clock for a fresh 30 minutes (or whatever).


  • To release a record lock without changing any data, an update command such as this may be used: update mytable set _locktime = null where .....


Example of record locking procedure - single record

1. middleware issues an IDENTITY command and then a SELECT .. FOR UPDATE to retrieve desired record.

2. middleware captures retrieval return code. If it is 7, this indicates that record(s) are already locked by someone else. Inform user and exit. QUISP example:

   #set USER = $getenv( "REMOTE_ADDR" )
   #sql identity @USER
   #sql select * from mytable where id = @reqid for update
   #if $sqlerror() = 7
    <h3>Record in use.. try later</h3>
    #exit
   #endif

3. otherwise, user has reserved the record and can edit it

4. user submits saved content

5. middleware issues an IDENTITY command and then an UPDATE command for record.

6. middleware captures update return code. If it is 7, this indicates that the record lock timed out, and someone else has locked (and perhaps unlocked) the record in the meantime. Inform user that her updates cannot be saved. QUISP example:

  #set USER = $getenv( "REMOTE_ADDR" )
  #sql
    #sqlbuild  @formmode  mytable  quote  noquote=id
  #endsql
  #if $sqlerror() = 7
   <h3>Your lock timed out.. 
     record now in use by someone else.. 
     updates can't be saved</h3>
   #exit
  #endif

7. otherwise, updates are saved, and record automatically unlocked.


Example of record locking procedure - multiple records

1. middleware issues an IDENTITY command and then a SELECT .. FOR UPDATE to retrieve desired records.

2. middleware captures retrieval return code. If it is 7 for any record, this indicates that the complete record set is not available. Release the record locks, inform user and exit.

3. otherwise, user has reserved the records and can edit them.

4. user submits saved content

5. middleware issues an IDENTITY command and then issues the above SELECT .. FOR UPDATE command again to ensure that all records still available. If this returns 7 for any record, the locks have expired and someone else has locked record(s) in the meantime; inform user that her updates cannot be saved.

6. middleware issues UPDATE command for records, and checks return code to be sure it is 0. If so, updates are saved and records automatically unlocked.


Operational details

SELECT..FOR UPDATE locks a record by recording the current time and identity in the record's _locktime and _lockowner fields. UPDATE unlocks a record by setting the _locktime field to null (but the _lockowner field is left alone).

A record is considered unlocked if the _locktime field is null or contains a time value that is old enough to be considered expired.

Time values are stored using YMDhm notation. 0 - 9 are represented naturally; 10-35 are represented using characters a through z; 36-61 are represented using characters A through Z.

The data returned by a SELECT..FOR UPDATE is retrieved immediately before the record lock is set; hence the returned _locktime and _lockowner fields are not current.

Since setting record lock(s) involves updating the table, a table write lock is set to cover the duration of the update. A multi-row SELECT .. FOR UPDATE causes the table write lock to be in effect until all rows are fetched, so all rows should be retrieved without delay.

If someone attempts to UPDATE or DELETE a set of records without locking them first, and the record set includes one or more records locked by others, the locked records will not be affected but all other records in the target set will be. The UPDATE or DELETE will return an error code of 7 to indicate that one or more target records was locked and not modified. This can be avoided if all processes lock records before UPDATING or DELETING them and, for multi-row updates where the record locks could time out, re-issueing the SELECT ..FOR UPDATE just before update to ensure that the records are still eligible for update (see multi-record example above).


Copyright Steve Grubb  


Markup created by unroff 1.0,    April 30, 2004.