shsql: record locking

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.