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 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

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.

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

Saved successfully!

Ooh no, something went wrong!