Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
CHAPTER 1 ■ DEVELOPING SUCCESSFUL ORACLE APPLICATIONS 33 OPS$TKYTE session(261,2586)> set transaction isolation level serializable; Transaction set. OPS$TKYTE session(261,2586)> update id_table 2 set id_value = id_value+1 3 where id_name = 'MY_KEY'; 1 row updated. OPS$TKYTE session(261,2586)> select id_value 2 from id_table 3 where id_name = 'MY_KEY'; ID_VALUE ---------- 1 Now, we’ll go to another SQL*Plus session and perform the same operation, a concurrent request for a unique ID: OPS$TKYTE session(271,1231)> set transaction isolation level serializable; Transaction set. OPS$TKYTE session(271,1231)> update id_table 2 set id_value = id_value+1 3 where id_name = 'MY_KEY'; This will block at this point, as only one transaction at a time can update the row. This demonstrates the first possible outcome, namely that we would block and wait for the row. But since we’re using SERIALIZABLE in Oracle, we’ll observe the following behavior as we commit the first session’s transaction: OPS$TKYTE session(261,2586)> commit; Commit complete. The second session will immediately display the following error: OPS$TKYTE session(271,1231)> update id_table 2 set id_value = id_value+1 3 where id_name = 'MY_KEY'; update id_table * ERROR at line 1: ORA-08177: can't serialize access for this transaction So, that database-independent piece of logic really isn’t database independent at all. Depending on the isolation level, it may not even perform reliably in a single database, let alone across any database! Sometimes we block and wait; sometimes we get an error message. To say the end user would be upset given either case (wait a long time, or wait a long time to get an error) is putting it mildly.
34 CHAPTER 1 ■ DEVELOPING SUCCESSFUL ORACLE APPLICATIONS This issue is compounded by the fact that our transaction is much larger than just outlined. The UPDATE and SELECT in the example are only two statements of potentially many other statements that make up our transaction. We have yet to insert the row into the table with this key we just generated and do whatever other work it takes to complete this transaction. This serialization will be a huge limiting factor in scaling. Think of the ramifications if this technique were used on web sites that process orders, and this was how we generated order numbers. There would be no multiuser concurrency, so we would be forced to do everything sequentially. The correct approach to this problem is to use the best code for each database. In Oracle, this would be (assuming the table that needs the generated primary key is T) as follows: create table t ( pk number primary key, ... ); create sequence t_seq; create trigger t_trigger before insert on t for each row begin select t_seq.nextval into :new.pk from dual; end; This will have the effect of automatically—and transparently—assigning a unique key to each row inserted. A more performance driven approach would be simply Insert into t ( pk, ... ) values ( t_seq.NEXTVAL, ... ); That is, skip the overhead of the trigger altogether (this is my preferred approach). In the first example, we’ve gone out of our way to use each database’s feature to generate a non-blocking, highly concurrent unique key, and we’ve introduced no real changes to the application code—all of the logic is contained in this case in the DDL. ■Tip The same effect can be achieved in the other databases using their built-in features or generating unique numbers. The CREATE TABLE syntax may be different, but the net results will be the same. Once you understand that each database will implement features in a different way, another example of defensive programming to allow for portability is to layer your access to the database when necessary. For example, say you are programming using JDBC. If all you use is straight SQL SELECTs, INSERTs, UPDATEs, and DELETEs, you probably do not need a layer of abstraction. You may very well be able to code the SQL directly in your application, as long as you limit the constructs you use to those supported by each of the databases you intend to support—and that you have verified work exactly the same (remember the NULL=NULL discussion!). Another approach that is both more portable and offers better performance is to use stored procedures to return resultsets. You will discover that every vendor’s database can return resultsets from stored procedures, but how they are returned is different. The actual source code you must write is different for different databases. Your two choices here are either to not use stored procedures to return resultsets or to implement different code for different databases. I would definitely follow the different code
- Page 28 and 29: ■SETTING UP YOUR ENVIRONMENT xxvi
- Page 30 and 31: ■SETTING UP YOUR ENVIRONMENT xxix
- Page 32 and 33: ■SETTING UP YOUR ENVIRONMENT xxxi
- Page 34 and 35: ■SETTING UP YOUR ENVIRONMENT xxxi
- Page 36 and 37: ■SETTING UP YOUR ENVIRONMENT xxxv
- Page 38 and 39: ■SETTING UP YOUR ENVIRONMENT xxxv
- Page 40 and 41: ■SETTING UP YOUR ENVIRONMENT xxxi
- Page 42 and 43: ■SETTING UP YOUR ENVIRONMENT xli
- Page 44 and 45: ■SETTING UP YOUR ENVIRONMENT xlii
- Page 46 and 47: CHAPTER 1 ■ ■ ■ Developing Su
- Page 48 and 49: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 50 and 51: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 52 and 53: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 54 and 55: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 56 and 57: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 58 and 59: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 60 and 61: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 62 and 63: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 64 and 65: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 66 and 67: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 68 and 69: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 70 and 71: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 72 and 73: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 74 and 75: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 76 and 77: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 80 and 81: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 82 and 83: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 84 and 85: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 86 and 87: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 88 and 89: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 90 and 91: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 92: CHAPTER 1 ■ DEVELOPING SUCCESSFUL
- Page 95 and 96: 50 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 97 and 98: 52 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 99 and 100: 54 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 101 and 102: 56 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 103 and 104: 58 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 105 and 106: 60 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 107 and 108: 62 CHAPTER 2 ■ ARCHITECTURE OVERV
- Page 110 and 111: CHAPTER 3 ■ ■ ■ Files In this
- Page 112 and 113: CHAPTER 3 ■ FILES 67 Without a pa
- Page 114 and 115: CHAPTER 3 ■ FILES 69 and even dat
- Page 116 and 117: CHAPTER 3 ■ FILES 71 all operatio
- Page 118 and 119: CHAPTER 3 ■ FILES 73 *.cluster_da
- Page 120 and 121: CHAPTER 3 ■ FILES 75 ops$tkyte@OR
- Page 122 and 123: CHAPTER 3 ■ FILES 77 • To maint
- Page 124 and 125: CHAPTER 3 ■ FILES 79 • Resource
- Page 126 and 127: CHAPTER 3 ■ FILES 81 3 l_dummy nu
CHAPTER 1 ■ DEVELOPING SUCCESSFUL ORACLE APPLICATIONS 33<br />
OPS$TKYTE session(261,2586)> set transaction isolation level serializable;<br />
Transaction set.<br />
OPS$TKYTE session(261,2586)> update id_table<br />
2 set id_value = id_value+1<br />
3 where id_name = 'MY_KEY';<br />
1 row updated.<br />
OPS$TKYTE session(261,2586)> select id_value<br />
2 from id_table<br />
3 where id_name = 'MY_KEY';<br />
ID_VALUE<br />
----------<br />
1<br />
Now, we’ll go to another SQL*Plus session <strong>and</strong> perform the same operation, a concurrent<br />
request for a unique ID:<br />
OPS$TKYTE session(271,1231)> set transaction isolation level serializable;<br />
Transaction set.<br />
OPS$TKYTE session(271,1231)> update id_table<br />
2 set id_value = id_value+1<br />
3 where id_name = 'MY_KEY';<br />
This will block at this point, as only one transaction at a time can update the row. This<br />
demonstrates the first possible outcome, namely that we would block <strong>and</strong> wait for the row.<br />
But since we’re using SERIALIZABLE in <strong>Oracle</strong>, we’ll observe the following behavior as we commit<br />
the first session’s transaction:<br />
OPS$TKYTE session(261,2586)> commit;<br />
Commit complete.<br />
The second session will immediately display the following error:<br />
OPS$TKYTE session(271,1231)> update id_table<br />
2 set id_value = id_value+1<br />
3 where id_name = 'MY_KEY';<br />
update id_table<br />
*<br />
ERROR at line 1:<br />
ORA-08177: can't serialize access for this transaction<br />
So, that database-independent piece of logic really isn’t database independent at all.<br />
Depending on the isolation level, it may not even perform reliably in a single database, let<br />
alone across any database! Sometimes we block <strong>and</strong> wait; sometimes we get an error message.<br />
To say the end user would be upset given either case (wait a long time, or wait a long time to<br />
get an error) is putting it mildly.