Kbase 16159: General rules for PROGRESS record reads ( buffer locking )
Autor |
  Progress Software Corporation - Progress |
Acesso |
  Público |
Publicação |
  8/24/1999 |
|
General rules for PROGRESS record reads ( buffer locking )
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
=====================================================================
These rules apply to PROGRESS versions 4 through 8.
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, 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.
Additional information on this parameter is available in
knowledgebase entry 19063 Title: What is the -rereadnolock parameter ?
23-Sep-96
24-Aug-99 EAG Added information about -rereadnolock
Progress Software Technical Support Note # 16159