If I were to write a book about how to build non-scalable
Oracle applications, then Don't use Bind Variables would be the
first and last chapter. This is a major cause of performance
issues and a major inhibitor of scalability. The way the Oracle
shared pool (a very important shared memory data structure)
operates is predicated on developers using bind variables. If you
want to make Oracle run slowly, even grind to a total halt
— just refuse to use them.
Bind variable is a placeholder in a query. For example, to
retrieve the record for employee 123, I can query:
select * from emp where empno = 123;
Alternatively, I can query:
select * from emp where empno = :empno;
In a typical system, you would query up employee 123 maybe once
and then never again. Later, you would query up employee 456,
then 789, and so on. If you use literals (constants) in the query
then each and every query is a brand new query, never before seen
by the database. It will have to be parsed, qualified (names
resolved), security checked, optimized, and so on — in
short, each and every unique statement you execute will have to
be compiled every time it is executed.
The second query uses a bind variable, :empno, the value of which
is supplied at query execution time. This query is compiled once
and then the query plan is stored in a shared pool (the library
cache), from which it can be retrieved and reused. The difference
between the two in terms of performance and scalability is huge,
dramatic even.
From the above description it should be fairly obvious that
parsing a statement with hard-coded variables (called a hard
parse) will take longer and consume many more resources than
reusing an already parsed query plan (called a soft parse). What
may not be so obvious is the extent to which the former will
reduce the number of users your system can support. Obviously,
this is due in part to the increased resource consumption, but an
even larger factor arises due to the latching mechanisms for the
library cache. When you hard parse a query, the database will
spend more time holding certain low-level serialization devices
called latches (see Chapter 3, Locking and Concurrency, for more
details). These latches protect the data structures in the shared
memory of Oracle from concurrent modifications by two sessions
(else Oracle would end up with corrupt data structures) and from
someone reading a data structure while it is being modified. The
longer and more frequently we have to latch these data
structures, the longer the queue to get these latches will
become. In a similar fashion to having long transactions running
under MTS, we will start to monopolize scarce resources. Your
machine may appear to be under-utilized at times — and yet
everything in the database is running very slowly. The likelihood
is that someone is holding one of these serialization mechanisms
and a line is forming — you are not able to run at top
speed.It only takes one ill behaved application in your database
to dramatically affect the performance of every other
application. A single, small application that does not use bind
variable will cause the relevant SQL of other well tuned
applications to get discarded from the shared pool over time. You
only need one bad apple to spoil the entire barrel.
If you use bind variables, then everyone who submits the same
exact query that references the same object will use the compiled
plan from the pool. You will compile your subroutine once and use
it over and over again. This is very efficient and is the way the
database intends you to work. Not only will you use fewer
resources (a soft parse is much less resource intensive), but
also you will hold latches for less time and need them less
frequently. This increases your performance and greatly increases
your scalability.
Just to give you a tiny idea of how huge a difference this
can make performance-wise, you only need to run a very small
test:
tkyte@TKYTE816> alter system flush shared_pool;
System altered.
Here I am starting with an 'empty' shared pool. If I was to run
this test more than one time, I would need to flush the shared
pool every time, or else the non-bind variable SQL below would,
in fact, be cached and appear to run very fast.
tkyte@TKYTE816> set timing on
tkyte@TKYTE816> declare
2 type rc is ref cursor;
3 l_rc rc;
4 l_dummy all_objects.object_name%type;
5 l_start number default dbms_utility.get_time;
6 begin
7 for i in 1 .. 1000
8 loop
9 open l_rc for
10 'select object_name
11 from all_objects
12 where object_id = ' || i;
13 fetch l_rc into l_dummy;
14 close l_rc;
15 end loop;
16 dbms_output.put_line
17 ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||
18 ' seconds...' );
19 end;
20 /
14.86 seconds...
PL/SQL procedure successfully completed.