Consultor Eletrônico



Kbase P4548: General rules for PROGRESS record reads ( buffer locking )
Autor   Progress Software Corporation - Progress
Acesso   Público
Publicação   5/23/2007
Status: Verified

GOAL:

General rules for PROGRESS record reads ( buffer locking )

GOAL:

General rules for record reads barring record caches and field-lists


GOAL:

Buffer Locking rules

GOAL:

Record Reads rules

FACT(s) (Environment):

Progress 7.X
Progress 8.X
Progress 9.X

FIX:

The following is an overview of the rules governing when PROGRESS performs a record read. This area became very complex in Version 8 when field-lists were introduced, but the following list provides a solid foundation for most cases.

TERM: record pool - The record pool refers to the set of database records stored on the PROGRESS client. In Version 6 and earlier, this was controlled by the -l session parameter. This should not be confused with the client's set of buffers, defined via the DEFINE BUFFER statement.

General rules for record reads, barring record caches and field-lists


1. Only a single copy of a record is allowed to exist at any one time in the client record pool. This means that in the following case we will end up with two buffers pointing at the same physical copy of customer 1.

DEFINE BUFFER cust FOR customer.
DEFINE BUFFER cust1 FOR customer.
FIND FIRST cust.
FIND FIRST cust1.

2. The FIND and GET statements release the record in the buffer (or buffers) before trying to locate the new one(s).

3. When doing a find by ROWID (or RECID), e.g.,
FIND cust WHERE ROWID(cust) = x.
the PROGRESS client first scans its record pool for the record.
If it finds it, and with a lock that is strong enough, it does not re-read the record from the database.

4. When doing any other FIND or GET, the record or records are read from the database. Once the client gets each record, it scans its record pool for a copy of the same record. If one is found, it must choose one.

The decision which record is picked is affected by field-lists, i.e., which fields each copy contains. If we ignore field-lists for now, the remaining rule is that the the old record is kept and the new one discarded, unless the old one is no-lock and the new one is locked.

Here are some examples how this works (these are true for V6 as well as V7 and V8).

1. DEFINE VAR r AS RECID.
FIND FIRST cust NO-LOCK.
r = RECID(cust).
FIND cust WHERE RECID(cust) = r NO-LOCK. /* reads a new version */

The last FIND releases the first record, and the client is left with no copy of it, so a new copy must be read.

2. DEFINE BUFFER cust FOR customer.
DEFINE BUFFER cust1 FOR customer.
DEFINE VAR r AS RECID.
FIND FIRST cust NO-LOCK.
r = RECID(cust).
FIND cust1 WHERE RECID(cust) = r NO-LOCK. /* gets the old copy */

The last FIND does not affect the record in the cust buffer, which stays around in the client record pool. When executing this statement, before accessing the database, the client scans the buffer pool, finds the right record and with the right lock, and connects it to buffer cust1.

3. DEFINE BUFFER cust FOR customer.
DEFINE BUFFER cust1 FOR customer.
FIND FIRST cust NO-LOCK.
FIND FIRST cust1 NO-LOCK. /* gets the old copy */

The last FIND does not affect the record in the cust buffer, which stays around in the client record pool. When executing this statement, the client first reads the record from the database. When it gets the record, it scans its record pool, finds another copy of the record, and keeps the old one (that is what the rules dictate).

You might think that PROGRESS should take the new record instead of the old copy, but there is one advantage to. keeping the old copy.
Because we keep the old copy, the record in the cust buffer does not change. If the new record were chosen, the record in this buffer would have changed even though the program never referred to it. Of course, if the last FIND use a lock, the record in the cust buffer would have changed.

4. /* recursive procedure recurse.p */
FIND FIRST state WHERE state.state = "AK" NO-LOCK NO-ERROR.
IF AVAILABLE state THEN DO:
MESSAGE state.state VIEW-AS ALERT-BOX.
END.
RUN recurse.p.

In this example, the FIND FIRST will not reflect any updates done by other users' sessions after the initial run of recurse.p.
PROGRESS will continue to use the copy of the record found in memory because the associated lock level is sufficient for each recursive call of the procedure, and because each recursive call creates a new buffer for state similar to the multiple buffers defined in the other examples above.

Placing the FIND...IF...END statement within a REPEAT loop results in a "fresh" read each time the loop iterates since there would then be only a single buffer defined for state.

The effect of record caches


Record caches were added in version 7. A record cache can be used with queries, and it ensures that a certain number of records around the current one stay in the client record pool after they are released from the buffer. Their effect on this issue can be demonstrated with the following example:

DEFINE BUFFER cust FOR customer.
DEFINE BUFFER cust1 FOR customer.
DEFINE QUERY q FOR cust CACHE 10.
DEFINE VAR r AS RECID.

OPEN QUERY q FOR EACH cust NO-LOCK.
GET FIRST q.
r = RECID(cust).
GET NEXT q. /* releases the first cust which stays in the
cache */
FIND cust1 WHERE RECID(cust) = r NO-LOCK. /* gets the old,
cached record */
GET PREV q. /* gets the old, cached record */
(A query with no CACHE clause defaults to no cache, but if a browser is used on it, the default changes to CACHE 50 if it is a single table query, and CACHE 30 if it is a join).


The effect of field-lists

Field-lists affect the logic of selecting which record is chosen when a new record is read and an old copy of it is found in the client record pool. The rules are elaborate, but they are based on the following:

1. A record in the client's buffer pool cannot be replaced with another copy which does not have at least all the same fields. For example, if buffer cust contains a record with fields A and B, it cannot be discarded and replaced with a record containing fields A and C.

2. Two copies of a record with two different subsets of fields cannot be merged into one record. The reason is that this may create an invalid record. For example, assume an item table with the following values:

item-number description
------------------------------
1 table
.2 chair

- a record contains item 1 (table).
- an application reads it no-lock with only the item-number (FIELDS(item-number)).
- another user then changes the same record to item-number 2, chair.
- the first user reads the same record again into another buffer, but this time only the description, i.e., FIELDS(description), and gets chair.
- if the two fields were merged into one record, we would get a record with item no. 1 and description chair, which makes no sense.

As a result, when a partial record is read from a database, and an old partial copy of the same record is found in the record pool, and we cannot be sure that either copy has enough fields to satisfy the two related queries, a complete copy of the record must be re-read from the database.


How to get a fresh copy of a record


In V7 and earlier, the only way to get a fresh copy of a no-lock record is either to ensure that it does not exist in the client's record pool (this is next to impossible with record caches), or to read in with a lock (share-lock will suffice).

In V8 and V9, the FIND CURRENT (or GET CURRENT) statement will do the trick.
It will get a fresh copy of the record if the record is no-lock (or share-lock with a dataserver that does not support share-locks, like Oracle or Sybase).

In Progress 8.3B and above, and in Progress V9 and above the -rereadnolock parameter can be used to get the latest version of the a record doing NO-LOCK reads..