Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
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
- Page 196 and 197: CHAPTER 4 ■ MEMORY STRUCTURES 151
- Page 198 and 199: CHAPTER 4 ■ MEMORY STRUCTURES 153
- Page 200 and 201: CHAPTER 5 ■ ■ ■ Oracle Proces
- Page 202 and 203: CHAPTER 5 ■ ORACLE PROCESSES 157
- Page 204 and 205: CHAPTER 5 ■ ORACLE PROCESSES 159
- Page 206 and 207: CHAPTER 5 ■ ORACLE PROCESSES 161
- Page 208 and 209: CHAPTER 5 ■ ORACLE PROCESSES 163
- Page 210 and 211: CHAPTER 5 ■ ORACLE PROCESSES 165
- Page 212 and 213: CHAPTER 5 ■ ORACLE PROCESSES 167
- Page 214 and 215: CHAPTER 5 ■ ORACLE PROCESSES 169
- Page 216 and 217: CHAPTER 5 ■ ORACLE PROCESSES 171
- Page 218 and 219: CHAPTER 5 ■ ORACLE PROCESSES 173
- Page 220 and 221: CHAPTER 5 ■ ORACLE PROCESSES 175
- Page 222 and 223: CHAPTER 5 ■ ORACLE PROCESSES 177
- Page 224 and 225: CHAPTER 5 ■ ORACLE PROCESSES 179
- Page 226 and 227: CHAPTER 5 ■ ORACLE PROCESSES 181
- Page 228 and 229: CHAPTER 6 ■ ■ ■ Locking and L
- Page 230 and 231: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 232 and 233: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 234 and 235: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 236 and 237: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 238 and 239: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 240 and 241: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 242 and 243: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 244 and 245: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 248 and 249: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 250 and 251: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 252 and 253: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 254 and 255: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 256 and 257: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 258 and 259: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 260 and 261: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 262 and 263: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 264 and 265: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 266 and 267: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 268 and 269: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 270 and 271: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 272 and 273: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 274 and 275: CHAPTER 6 ■ LOCKING AND LATCHING
- Page 276 and 277: CHAPTER 7 ■ ■ ■ Concurrency a
- Page 278 and 279: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 280 and 281: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 282 and 283: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 284 and 285: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 286 and 287: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 288 and 289: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 290 and 291: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 292 and 293: CHAPTER 7 ■ CONCURRENCY AND MULTI
- Page 294 and 295: CHAPTER 7 ■ CONCURRENCY AND MULTI
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