The correct approach to this problem would be to use the best
code for each database. In Oracle this would be (assuming the
table that needs the generated primary key is T):
Click here for code example 1.
This will have the effect of automatically, and transparently,
assigning a unique key to each row inserted. The same effect can
be achieved in the other databases using their types — the
create tables syntax will be different, the net results will be
the same. Here, we have gone out of our way to use each databases
feature to generate a non-blocking, highly concurrent
unique key, and have introduced no real changes to the
application code — all of the logic is contained in this
case in the DDL.
Another example of defensive programming to allow for portability
is, once you understand that each database will implement
features in a different way, to layer your access to the
database when necessary. Let's 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
constructs supported by each of the databases you intend to
support. Another approach that is both more portable and offers
better performance, would be 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 would be to either not use stored
procedures to return resultsets, or to implement different code
for different databases. I would definitely follow the 'different
code for different vendors' method, and use stored procedures
heavily. This apparently seems to increase the amount of time it
would take to implement on a different database. However, you
will find it is actually easier to implement on multiple
databases with this approach. Instead of having to find the
perfect SQL that works on all databases (perhaps better on
some than on others), you will implement the SQL that works best
on that database. You can do this outside of the application
itself, giving you more flexibility in tuning the application. We
can fix a poorly performing query in the database itself, and
deploy that fix immediately, without having to patch the
application. Additionally, you can take advantage of vendor
extensions to SQL using this method freely. For example, Oracle
supports hierarchical queries via the CONNECT BY operation in its
SQL. This unique feature is great for resolving recursive
queries. In Oracle you are free to utilize this extension to SQL
since it is 'outside' of the application (hidden in the
database). In other databases, you would use a temporary table
and procedural code in a stored procedure to achieve the same
results, perhaps. You paid for these features so you might as
well use them.
These are the same techniques developers who implement multi-
platform code utilize. Oracle Corporation for example uses this
technique in the development of its own database. There is a
large amount of code (a small percentage of the database code
overall) called OSD (Operating System
Dependent) code that is implemented specifically for each
platform. Using this layer of abstraction, Oracle is able to make
use of many native OS features for performance and integration,
without having to rewrite the large majority of the database
itself. The fact that Oracle can run as a multi-threaded
application on Windows and a multi-process application on UNIX
attests to this feature. The mechanisms for inter-process
communication are abstracted to such a level that they can be re-
implemented on an OS-by-OS basis, allowing for radically
different implementations that perform as well as an application
written directly, and specifically, for that platform.
In addition to SQL syntactic differences, implementation
differences, and differences in performance of the same query in
different databases outlined above, there are the issues of
concurrency controls, isolation levels, query consistency, and so
on. We cover these items in some detail in Chapter 3, Locking and
Concurrency, and Chapter 4, Transactions of this book, and see
how their differences may affect you. SQL92 attempted to give a
straightforward definition of how a transaction should work, how
isolation levels are to be implemented, but in the end, you'll
get different results from different databases. It is all due to
the implementation. In one database an application will deadlock
and block all over the place. In another database, the same exact
application will not — it will run smoothly. In one
database, the fact that you did block (physically serialize) was
used to your advantage and when you go to deploy on another
database, and it does not block, you get the wrong answer.
Picking an application up and dropping it on another database
takes a lot of hard work and effort, even if you followed the
standard 100 percent.