Using names and lockhandles to identify locks is considered safer than using integer identifiers directly because naming standards can be adopted to virtually guarantee that different ap
Trang 14.1.2.5.3 Example
The following SQL*Plus script displays a screen message and pauses for ten seconds before continuing:
prompt **************************************
prompt * This is a very important message
prompt * ************************************
BEGIN
DBMS_LOCK.SLEEP(10);
END;
/
Applications using resources to which concurrent access is restricted may need to try again later if the
resource is busy The SLEEP procedure provides a mechanism for including low−overhead wait times into PL/SQL programs After waiting, an application can retry the operation that failed to acquire the busy
resource
4.1.3 Tips on Using DBMS_LOCK
In this section I've pulled together a number of best practices for using the DBMS_LOCK package
4.1.3.1 Named locks or lock ids?
Oracle provides two methods of identifying and manipulating user locks: integer lock identifiers and handles for named locks Using names and lockhandles to identify locks is considered safer than using integer
identifiers directly because naming standards can be adopted to virtually guarantee that different applications will not use the same lock for different purposes Therefore, best practices for using DBMS_LOCK include the use of named locks and lockhandles
4.1.3.2 Issues with named locks
There are a couple of drawbacks to using named locks that are worth pointing out In particular:
•
Named locks are recorded in the catalog, and thus may be slower
•
The DBMS_LOCK.ALLOCATE_UNIQUE procedure issues a COMMIT
•
Applications need to keep track of lockhandles for each named lock used
It is worth investigating these drawbacks and developing techniques to minimize their impact, thus further encouraging the use of named locks
4.1.3.3 Performance of named locks
We can investigate the performance penalty for using named locks, and quantify that penalty in a relatively straightforward manner Consider the following PL/SQL script:
/* Filename on companion disk: lock1.sql */*
set timing on
set serveroutput on size 100000
DECLARE
Trang 2lockname VARCHAR2(30) := 'OPBIP_TEST_LOCK_10';
lockhandle VARCHAR2(128);
lockid INTEGER := 99999;
call_status INTEGER;
timer NUMBER;
BEGIN
/*
|| timed test using lockhandles
*/
timer := DBMS_UTILITY.GET_TIME;
DBMS_LOCK.ALLOCATE_UNIQUE(lockname,lockhandle);
FOR i IN 1 10000
LOOP
call_status := DBMS_LOCK.REQUEST(lockhandle,timeout=>0);
call_status := DBMS_LOCK.RELEASE(lockhandle);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Using lockhandles: '||
TO_CHAR(ROUND((DBMS_UTILITY.GET_TIME−timer)/100,2)) ||' secs');
/*
|| timed test using lockids
*/
timer := DBMS_UTILITY.GET_TIME;
FOR i IN 1 10000
LOOP
call_status := DBMS_LOCK.REQUEST(lockid,timeout=>0);
call_status := DBMS_LOCK.RELEASE(lockid);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Using lockids: '||
TO_CHAR(ROUND((DBMS_UTILITY.GET_TIME−timer)/100,2)) ||' secs');
END;
The PL/SQL block reports on the elapsed times to request and release a lock 10,000 times using either a lockhandle or an integer lock identifier The test yielded the following results on a Personal Oracle7 database with no other activity:
SQL> @l2
Using lockhandles: 9.57 secs
Using lockids: 3.02 secs
PL/SQL procedure successfully completed.
real: 12740
SQL> spool off
These results confirm that use of lockhandles is significantly slower than use of lock identifiers However, the results also indicate that the overhead of named locks was less than one−thousandth of a second per usage Thus, the performance impact of using named locks is negligible and is probably not a legitimate concern for most applications
4.1.3.4 ALLOCATE_UNIQUE drawbacks
The other issues mentioned with named locks are usage related The ALLOCATE_UNIQUE procedure needs
to be called to identify a lockhandle for each named lock This procedure issues a COMMIT, which presents some usability issues For one, the procedure cannot be called from a database trigger, so using named locks from a database trigger requires that the lockhandle be acquired outside of the trigger and saved for use in the trigger Another problem is the COMMIT itself: an application may want to utilize a named lock but not necessarily COMMIT the current transaction Thus, it is desirable when using named locks to limit the
number of calls to ALLOCATE_UNIQUE to exactly one call per named lock used
Trang 34.1.3.5 Optimizing named locks
One way to achieve the objective of minimizing calls to ALLOCATE_UNIQUE is to use private package global variables to store lockhandles for each named lock A function that will return the lockhandle can then
be written, calling ALLOCATE_UNIQUE only if the lockhandle has not been previously identified This technique is illustrated as follows:
PACKAGE BODY print_pkg
IS
/* private globals for lock identification */
printer_lockname VARCHAR2(128) := 'printer_lock';
printer_lockhandle VARCHAR2(128);
FUNCTION get_printer_lockhandle
RETURN VARCHAR2
IS
BEGIN
IF printer_lockhandle IS NULL
THEN
DBMS_LOCK.ALLOCATE_UNIQUE
(lockname => printer_lockname
,lockhandle => printer_lockhandle);
END IF;
RETURN printer_lockhandle;
END get_printer_lockhandle;
END print_pkg;
Using this technique ensures that the ALLOCATE_UNIQUE procedure is called only once per session
requiring use of the printer lock The lock can even be used in a database trigger if the function
get_printer_lockhandle has been called prior to the triggering event
One drawback to this technique is code redundancy: each named lock used by an application requires adding a specific package global variable for the lockhandle and an associated function to return it Referencing a new named lock in an application involves adding a nontrivial amount of code before it can be used
4.1.3.6 REQUEST or CONVERT?
Another usability issue with DBMS_LOCK (not specific to named locks): applications using multiple lock modes need to have intelligence about whether to call the REQUEST function or the CONVERT function If the user has requested and received a lock in a specific mode, then that mode can only be changed by calling CONVERT On the other hand, a lock conversion can only take place if it is preceded by a successful call to REQUEST Getting it right can mean developing code that checks and tracks return codes from the calls to these two procedures
4.1.4 DBMS_LOCK Examples
In response to the usability issues described in the previous section, I have developed a utility package called dblock to simplify, and consequently encourage, the use of named locks in PL/SQL applications
4.1.4.1 The dblock package
The dblock package specification follows:
/* Filename on on companion disk: dblock.sql */*
CREATE OR REPLACE PACKAGE dblock
/*
|| Adds value to DBMS_LOCK by allowing easier manipulation
Trang 4|| of named locks Calling programs use lock names only,
|| corresponding lockhandles are automatically identified,
|| used and saved for subsequent use.
||
||
|| Author: John Beresniewicz, Savant Corp
||
|| 10/26/97: added expiration_secs_IN to lockhandle
|| 10/21/97: added release
|| 10/21/97: added dump_lockhandle_tbl
|| 10/17/97: created
||
|| Compilation Requirements:
||
|| EXECUTE on DBMS_LOCK
|| EXECUTE on DBMS_SESSION
||
|| Execution Requirements:
||
*/
AS
/* variables to anchor other variables */
lockname_var VARCHAR2(128);
lockhandle_var VARCHAR2(128);
/*
|| returns TRUE if a COMMIT has taken place between
|| subsequent calls to the function
|| NOTE: returns TRUE on first call in session
*/
FUNCTION committed_TF RETURN BOOLEAN;
/*
|| returns lockhandle for given lockname, only calls
|| DBMS_LOCK.ALLOCATE_UNIQUE if lockhandle has not been
|| previously determined
*/
FUNCTION lockhandle
(lockname_IN IN lockname_var%TYPE
,expiration_secs_IN IN INTEGER := 864000)
RETURN lockhandle_var%TYPE;
/*
|| returns TRUE if named lock is acquired in mode
|| specified
*/
FUNCTION get_lock_TF
(lockname_IN IN lockname_var%TYPE
,mode_IN IN INTEGER := DBMS_LOCK.x_mode
,timeout_IN IN INTEGER := 1
,release_on_commit_TF IN BOOLEAN := FALSE)
RETURN BOOLEAN;
/* releases named lock */
PROCEDURE release (lockname_IN IN lockname_var%TYPE);
/* print contents of lockhandle_tbl for debugging */
PROCEDURE dump_lockhandle_tbl;
END dblock;
The dblock programs allow the user to identify and acquire locks by name only Lockhandles for each named lock are managed within the package, transparent to the application The package associates locknames with lockhandles by using a private global PL/SQL table called lockhandle_tbl The table is defined as follows:
/* rectype to pair handles with names */
Trang 5TYPE handle_rectype IS RECORD
(name lockname_var%TYPE
,handle lockhandle_var%TYPE
);
/* table to store lockhandles by name */
TYPE handle_tbltype IS TABLE OF handle_rectype
INDEX BY BINARY_INTEGER;
lockhandle_tbl handle_tbltype;
4.1.4.1.1 The lockhandle function
The lockhandle function takes a lockname as an IN parameter and returns the associated lockhandle If the lockhandle has already been identified and stored in the lockhandle_tbl table, it is returned directly
Otherwise, DBMS_LOCK.ALLOCATE_UNIQUE is called to determine the lockhandle, which is then stored
in lockhandle_tbl and is also returned to the caller Here is the body of lockhandle:
/* Filename on companion disk: dblock.sql */*
FUNCTION lockhandle
(lockname_IN IN lockname_var%TYPE
,expiration_secs_IN IN INTEGER := 864000)
RETURN lockhandle_var%TYPE
IS
call_status INTEGER;
temp_lockhandle lockhandle_var%TYPE;
temp_index BINARY_INTEGER;
BEGIN
/*
|| if lockhandle_tbl empty must call ALLOCATE_UNIQUE
*/
IF lockhandle_tbl.COUNT = 0
THEN
DBMS_LOCK.ALLOCATE_UNIQUE
(lockname => lockname_IN
,lockhandle => temp_lockhandle
,expiration_secs => expiration_secs_IN);
lockhandle_tbl(1).handle := temp_lockhandle;
lockhandle_tbl(1).name := lockname_IN;
/*
|| check lockhandle_tbl for matching lockname
*/
ELSE
FOR i IN lockhandle_tbl.FIRST lockhandle_tbl.LAST
LOOP
IF lockhandle_tbl(i).name = lockname_IN
THEN
temp_lockhandle := lockhandle_tbl(i).handle;
END IF;
END LOOP;
END IF;
/*
|| if temp_lockhandle still null, call ALLOCATE_UNIQUE
|| and load entry into lockhandle_tbl
*/
IF temp_lockhandle IS NULL
THEN
DBMS_LOCK.ALLOCATE_UNIQUE
(lockname => lockname_IN