Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005

rekharaghuram
from rekharaghuram More from this publisher
05.11.2015 Views

CHAPTER 6 ■ LOCKING AND LATCHING 201 back (in which case the blocked session succeeds). Another case involves tables linked together via referential integrity constraints. An insert into a child table may become blocked if the parent row it depends on is being created or deleted. Blocked INSERTs typically happen with applications that allow the end user to generate the primary key/unique column value. This situation is most easily avoided by using a sequence to generate the primary key/unique column value. Sequences were designed to be a highly concurrent method of generating unique keys in a multiuser environment. In the event that you cannot use a sequence, you can use the following technique, which avoids the issue by using manual locks implemented via the built-in DBMS_LOCK package. ■Note The following example demonstrates how to prevent a session from blocking on an insert due to a primary key or unique constraint. It should be stressed that the “fix” demonstrated here should be considered a short-term solution while the application architecture itself is inspected. This approach adds obvious overhead and should not be implemented lightly. A well-designed application would not encounter this issue. This should be considered a last resort and is definitely not something you want to do to every table in your application “just in case.” With inserts, there’s no existing row to select and lock; there’s no way to prevent others from inserting a row with the same value, thus blocking our session and causing us to wait indefinitely. Here is where DBMS_LOCK comes into play. To demonstrate this technique, we will create a table with a primary key and a trigger that will prevent two (or more) sessions from inserting the same values simultaneously. The trigger will use DBMS_UTILITY.GET_HASH_VALUE to hash the primary key into some number between 0 and 1,073,741,823 (the range of lock ID numbers permitted for our use by Oracle). In this example, I’ve chosen a hash table of size 1,024, meaning we will hash our primary keys into one of 1,024 different lock IDs. Then we will use DBMS_LOCK.REQUEST to allocate an exclusive lock based on that ID. Only one session at a time will be able to do that, so if someone else tries to insert a record into our table with the same primary key, then that person’s lock request will fail (and the error resource busy will be raised): ■Note To successfully compile this trigger, execute permission on DBMS_LOCK must be granted directly to your schema. The privilege to execute DBMS_LOCK may not come from a role. scott@ORA10G> create table demo ( x int primary key ); Table created. scott@ORA10G> create or replace trigger demo_bifer 2 before insert on demo 3 for each row 4 declare 5 l_lock_id number;

202 CHAPTER 6 ■ LOCKING AND LATCHING 6 resource_busy exception; 7 pragma exception_init( resource_busy, -54 ); 8 begin 9 l_lock_id := 10 dbms_utility.get_hash_value( to_char( :new.x ), 0, 1024 ); 11 if ( dbms_lock.request 12 ( id => l_lock_id, 13 lockmode => dbms_lock.x_mode, 14 timeout => 0, 15 release_on_commit => TRUE ) 0 ) 16 then 17 raise resource_busy; 18 end if; 19 end; 20 / Trigger created. Now, if in two separate sessions we execute the following: scott@ORA10G> insert into demo values ( 1 ); 1 row created. it will succeed in the first session but immediately issue the following in the second session: scott@ORA10G> insert into demo values ( 1 ); insert into demo values ( 1 ) * ERROR at line 1: ORA-00054: resource busy and acquire with NOWAIT specified ORA-06512: at "SCOTT.DEMO_BIFER", line 14 ORA-04088: error during execution of trigger 'SCOTT.DEMO_BIFER' The concept here is to take the supplied primary key value of the table protected by the trigger and put it in a character string. We can then use DBMS_UTILITY.GET_HASH_VALUE to come up with a “mostly unique” hash value for the string. As long as we use a hash table smaller than 1,073,741,823, we can “lock” that value exclusively using DBMS_LOCK. After hashing, we take that value and use DBMS_LOCK to request that lock ID to be exclusively locked with a timeout of ZERO (this returns immediately if someone else has locked that value). If we timeout or fail for any reason, we raise ORA-54 Resource Busy. Otherwise, we do nothing—it is OK to insert, we won’t block. Of course, if the primary key of your table is an INTEGER and you don’t expect the key to go over 1 billion, you can skip the hash and just use the number as the lock ID. You’ll need to play with the size of the hash table (1,024 in this example) to avoid artificial resource busy messages due to different strings hashing to the same number. The size of the hash table will be application (data)-specific, and it will be influenced by the number of concurrent insertions as well. Lastly, bear in mind that although Oracle has unlimited row-level locking, it has a finite number of enqueue locks. If you insert lots of rows this way without committing in your session, then you might find that you create so many enqueue locks that you exhaust the system of enqueue resources (you exceed the maximum value set in the

202<br />

CHAPTER 6 ■ LOCKING AND LATCHING<br />

6 resource_busy exception;<br />

7 pragma exception_init( resource_busy, -54 );<br />

8 begin<br />

9 l_lock_id :=<br />

10 dbms_utility.get_hash_value( to_char( :new.x ), 0, 1024 );<br />

11 if ( dbms_lock.request<br />

12 ( id => l_lock_id,<br />

13 lockmode => dbms_lock.x_mode,<br />

14 timeout => 0,<br />

15 release_on_commit => TRUE ) 0 )<br />

16 then<br />

17 raise resource_busy;<br />

18 end if;<br />

19 end;<br />

20 /<br />

Trigger created.<br />

Now, if in two separate sessions we execute the following:<br />

scott@ORA10G> insert into demo values ( 1 );<br />

1 row created.<br />

it will succeed in the first session but immediately issue the following in the second session:<br />

scott@ORA10G> insert into demo values ( 1 );<br />

insert into demo values ( 1 )<br />

*<br />

ERROR at line 1:<br />

ORA-00054: resource busy <strong>and</strong> acquire with NOWAIT specified<br />

ORA-06512: at "SCOTT.DEMO_BIFER", line 14<br />

ORA-04088: error during execution of trigger 'SCOTT.DEMO_BIFER'<br />

The concept here is to take the supplied primary key value of the table protected by the<br />

trigger <strong>and</strong> put it in a character string. We can then use DBMS_UTILITY.GET_HASH_VALUE to come<br />

up with a “mostly unique” hash value for the string. As long as we use a hash table smaller<br />

than 1,073,741,823, we can “lock” that value exclusively using DBMS_LOCK.<br />

After hashing, we take that value <strong>and</strong> use DBMS_LOCK to request that lock ID to be exclusively<br />

locked with a timeout of ZERO (this returns immediately if someone else has locked that<br />

value). If we timeout or fail for any reason, we raise ORA-54 Resource Busy. Otherwise, we do<br />

nothing—it is OK to insert, we won’t block.<br />

Of course, if the primary key of your table is an INTEGER <strong>and</strong> you don’t expect the key to go<br />

over 1 billion, you can skip the hash <strong>and</strong> just use the number as the lock ID.<br />

You’ll need to play with the size of the hash table (1,024 in this example) to avoid artificial<br />

resource busy messages due to different strings hashing to the same number. The size of the<br />

hash table will be application (data)-specific, <strong>and</strong> it will be influenced by the number of concurrent<br />

insertions as well. Lastly, bear in mind that although <strong>Oracle</strong> has unlimited row-level<br />

locking, it has a finite number of enqueue locks. If you insert lots of rows this way without<br />

committing in your session, then you might find that you create so many enqueue locks that<br />

you exhaust the system of enqueue resources (you exceed the maximum value set in the

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!