Mohamed Houri’s Oracle Notes

May 18, 2013

Literal, bind variable and adaptive cursor sharing: simplify them please!!!

Filed under: Oracle — hourim @ 11:00 am

When you find yourself very often typing the same set of sql statements you will end up by writing a sql script in which will be collected those sql statements. As such, you will have avoided repetitive sql typing.

When you find yourself very often writing the same set of phrases to explain an Oracle concept you will end up by writing a blog article in which will be collected those phrases. As such, you will be referring to that blog article instead of re-typing the same phrases.

When it is question of pros and cons of using literals, bind variables and cursor sharing, I believe, I’ve reached the point, where writing down my corresponding repetitive phrases become necessary.  So, please, take this article as a summary for me and for those who want to deepen a little bit their knowledge of these interacting concepts.

Let’s start now.

If you want to develop a non scalable and a non available Oracle application running slowly, then you have only one thing to do: “don’t use bind variable’’.  Oracle architecture is so that sharing memory (SGA-Library cache) represents a crucial aspect Oracle engineers have to know and to master. However, as it is always the case with Oracle database, despite this feature is very important it has  few drawbacks that are worth to be known. While bind variables allow sharing of parent cursors (SQL code) they also allow sharing of execution plans (child cursor). Sharing the same execution plan for different bind variables is not always optimal as far as different bind variables can generate different data volume. This is why Oracle introduces bind variable peeking feature which allows Oracle to peek at the bind variable value and give it the best execution plan possible. However, bind variable peeking occurs only at hard parse time which means as far as the query is not hard parsed it will share the same execution plan that corresponds to the last hard parsed bind variable. In order to avoid such situation Oracle introduces in its 11gR2 release, Adaptive Cursor Sharing allowing Oracle to adapt itself to the bind variable when necessary without having to wait for a hard parse of the query.

1.Using Literal variables

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'Y1';

 COUNT(*) MAX(COL2)
 ---------- -----------------------------------------
 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 761479741

 -------------------------------------------------------------------------------------
 | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 -------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
 |   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
 |   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
 |*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
 -------------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 3 - access("FLAG"='Y1')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'N1';

 COUNT(*) MAX(COL2)
 ---------- ---------------------------------------------
 49998 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 3693069535
 ---------------------------------------------------------------------------
 | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 |   0 | SELECT STATEMENT   |      |       |       |   216 (100)|          |
 |   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
 |*  2 |   TABLE ACCESS FULL| T1   | 55095 |  1614K|   216   (2)| 00:00:02 |
 ---------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 2 - filter("FLAG"='N1')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'Y2';

 COUNT(*) MAX(COL2)
 ---------- -----------------------------------------
 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 761479741
 -------------------------------------------------------------------------------------
 | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 -------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
 |   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
 |   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
 |*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
 -------------------------------------------------------------------------------------

 Predicate Information (identified by operation id):
 ---------------------------------------------------
 3 - access("FLAG"='Y2')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'N2';

 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
 49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 3693069535
 ---------------------------------------------------------------------------
 | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 |   0 | SELECT STATEMENT   |      |       |       |   216 (100)|          |
 |   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
 |*  2 |   TABLE ACCESS FULL| T1   | 55251 |  1618K|   216   (2)| 00:00:02 |
 ---------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 2 - filter("FLAG"='N2')
 

I executed the same query using 4 different hard coded variables. For each literal variable I got the adequat execution plan. That’s very nice from this point of view. But, if I consult the library cache I will see the damage I have caused

 SQL> select sql_id, substr(sql_text,1,30), executions
   2  from v$sql
   3  where sql_text like '%literal_variable%'
   4  and   sql_text not like '%v$sql%';

 SQL_ID        SUBSTR(SQL_TEXT,1,30)          EXECUTIONS
 ------------- ------------------------------ ----------
 axuhh2rjx0jc7 select /*+ literal_variable */          1---> sql code is not re-executed
 c6yy4pad9fd0x select /*+ literal_variable */          1---> sql code is not shared
 45h3507q5r318 select /*+ literal_variable */          1---> the same sql seems for the CBO
 76q7p8q473cdq select /*+ literal_variable */          1---> to be a new sql statement
 

There is 1 record for each execution.  If you repeat the same sql statement changing only the value of the flag you will end up by having as much as records in v$sql as the number of different literal values you will used.

2. Using bind variables

So what will I point out if I prefer using bind variables instead of these literal ones?

 SQL> var n varchar2(2);
 SQL> exec :n := ’Y1’ ---> bind favoring index range scan
 SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0

-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’N1’ ---> bind favoring full table scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’Y2’ ---> bind favoring index range scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’N2’ ---> bind favoring table scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

Have you already pointed out something very clear? They (4 selects) share the same execution plan which is the plan that was generated for the first hard parsed bind variable ‘Y1’. As far as this one favors an index range scan access it shares that plan with all successive identical queries having the same sql_id. But spot with me how the library cache looks now very attractive

 SQL> select sql_id, substr(sql_text,1,30), executions
  2  from v$sql
  3  where sql_text like '%bind_variable%'
  4  and   sql_text not like '%v$sql%';

 SQL_ID        SUBSTR(SQL_TEXT,1,30)          EXECUTIONS
  ------------- ------------------------------ ----------
  8xujk8a1g65x6 select /*+ bind_variable */ co          4  ---> one sql code and 4 executions
 

Let me, at this particular step, make a break point.

  • SQL statements using literal variables represent a non-sharable SQL which can get the best execution plans each time at a cost in optimization overheads (memory, CPU and latching).
  • SQL statements using bind variables are represented by a unique sql_id (or a very small number of copies) in the library cache statement that are re-executed saving memory and CPU parse time. But this resource saving makes SQL statements sharing the same execution plan; that is the plan corresponding to the first bind value Oracle peeked at for the plan optimization during the hard parse time even if this plan is not optimal for the next bind variable value.

So what? Shall we use literal or bind variables? The best answer I have found to this question is that of Tom Kyte “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 the last chapter”.

3. Adaptive cursor sharing came to the rescue

Adaptive cursor sharing (ACS) is a feature introduced in the Oracle 11g release to allow, under certain circumstances, the Cost Based Optimizer (CBO) to adapt itself, peeks at the bind variable and generate the best plan possible without waiting for a hard parse to occur. Below is presented the ACS working algorithm:

ACS

So far we are using bind variables. Our SQL query is then bind sensitive. Ins’t it?

SQL> alter system flush shared_pool;

SQL> exec :n := 'N1';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 46667 |  1367K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

SQL> exec :n := 'Y1';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 46667 |  1367K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Let’s see now after several executions of the same query if ACS kicks off or not. Remember that the first condition for ACS to kick off is that our cursor has to be bind sensitive. In the next query you should read the “I” prompts as Is_bind_aware , Is_bind_sensitive  and Is_shareable respectively:

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ------------------- ---------- ---------------
8xujk8a1g65x6            0 N N Y 9686445671300360182    5         3724264953

After 5 executions the cursor is still not bind sensitive. In fact, to be so, the bind variable should have histograms

SQL> exec dbms_stats.gather_table_stats(USER,'T1',method_opt=>'FOR COLUMNS flag SIZE AUTO',no_invalidate=>FALSE);

SQL> exec :n := 'Y1';
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

We got an index range scan for the variable that favors an index range scan. That’s fine. Let’s see now if our cursor is bind sensitive

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - ------------------- ----------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182   1         3625400295

Yes it is. But it is not yet bind aware.

SQL> exec :n := 'N2';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

I executed the query with the bind variable that favors a full table scan but I shares the preceding execution plan. Let’s see if our cursor is bind aware


SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182                               2      3625400295

Still not. The query needs a warm up period before being bind aware.  So let’s execute again


SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

SQL_ID  8xujk8a1g65x6, child number 1

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 50894 |  1491K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Finally we got a full table scan. Is this due to ACS?


SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182                               2      3625400295
8xujk8a1g65x6            1 Y Y Y 9686445671300360182                               1      3724264953

Yes it is. Look how the second line (child number 1) is bind sensitive, bind aware and shareable. This is how ACS works.

Now, if I execute the same query with a bind variable that favors an index range scan, ACS will give me the INDEX RANGE SCAN plan


SQL> exec :n := 'Y2';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

SQL_ID  8xujk8a1g65x6, child number 2
-------------------------------------
Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y N 9686445671300360182                               2      3625400295
8xujk8a1g65x6            1 Y Y Y 9686445671300360182                               2      3724264953
8xujk8a1g65x6            2 Y Y Y 9686445671300360182                               1      3625400295

Spot how a new child cursor (child number 2) has been created and it is bind sensitive, bind aware and shareable. Playing with those bind variables values combinations I ended up by having two child cursors, one for (3724264953) full table scan, and the other one (3625400295) for the index range scan that are both shareable. Thanks to these two child cursors (until they are flushed out, or something disturbs their good working), the CBO will be alternating between the two executions plans giving each bind variable its corresponding execution plan.

For those who want to play with this example, you can use Dominic brooks model reproduced below:

create table t1
(col1  number
,col2  varchar2(50)
,flag  varchar2(2));

insert into t1
select rownum
,      lpad('X',50,'X')
,      case when rownum = 1
then 'Y1'
when rownum = 2
then 'Y2'
when mod(rownum,2) = 0
then 'N1'
else 'N2'
end
from   dual
connect by rownum <= 100000;

create index i1 on t1 (flag);

And the is_bind_sens.sql script is

select sql_id
,      child_number
,      is_bind_aware
,      is_bind_sensitive
,      is_shareable
,      to_char(exact_matching_signature) sig
,      executions
,      plan_hash_value
from   v$sql
where  sql_text like '%bind_variable %'
and    sql_text not like '%v$sql%';
Advertisements

4 Comments »

  1. Hi Mohamed,

    This is an excellent summary.
    I am just wondering if there is still some relation between ACS and the parameter ‘cursor_sharing’. This point is not clear for me.
    By this i mean does ACS works the same way if cursor_sharing is set to force / similar, or it has nothing to do with ?

    Best regards
    Frank

    Comment by Polet Frank — May 20, 2013 @ 4:26 pm | Reply

  2. Franck,

    There are two main conditions for which ACS kicks off:

    1. We need to use bind variable (in order to be candidate for bind sensitiveness)
    2. CBO need to be bind aware

    ACS doesn’t care whether the bind variable has been generated internally by Oracle (cursor_sharing = force or similar i.e.:SYS_B_0) or generated by the application itself (i.e.:B1).

    Put it simply, if for example, in my application I am using the following query

    select count(*), max(col2) from t1 where flag = ‘Y1’;
    

    Then, in order to eventually benefit from the ACS feature, I need to set cursor_sharing=FORCE. As such there will be a literal replacement and the CBO will see my query as if it has been written like:

    select count(*), max(col2) from t1 where flag = :SYS_B_0;
    

    But if, in my application, I am submitting to the CBO a query using bind variable:

    select count(*), max(col2) from t1 where flag = :n;
    

    Then, I don’t need to have cursor_sharing parameter set to any other value then its default one (exact).

    There is good article done by the Oracle optimizer team which deals in detail about your question
    https://blogs.oracle.com/optimizer/entry/explain_adaptive_cursor_sharing_behavior_with_cursor_sharing_similar_and_force

    Best regards

    Comment by hourim — May 20, 2013 @ 5:21 pm | Reply

  3. […] via Literal, bind variable and adaptive cursor sharing: simplify them please!!! | Mohamed Houri’s Orac… […]

    Pingback by Literal, bind variable and adaptive cursor sharing: simplify them please | Links to Various sources — July 16, 2016 @ 10:54 am | Reply

  4. Yes you have simplified it ;

    How to make a cursor bind-sensitive ? Does the below satisfy
    a. Column used in equality or a range predicate
    b. Collect histogram information on the column and/or index (using gather stats )

    Comment by udayjampani — May 8, 2017 @ 10:29 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

Tony's Oracle Tips

Tony Hasler's light hearted approach to learning about Oracle

Richard Foote's Oracle Blog

Focusing Specifically On Oracle Indexes, Database Administration and Some Great Music

Hatem Mahmoud Oracle's blog

Just another Oracle blog : Database topics and techniques

Mohamed Houri’s Oracle Notes

Qui se conçoit bien s’énonce clairement

Oracle Diagnostician

Performance troubleshooting as exact science

Raheel's Blog

Things I have learnt as Oracle DBA

Coskan's Approach to Oracle

What I learned about Oracle

So Many Oracle Manuals, So Little Time

“Books to the ceiling, Books to the sky, My pile of books is a mile high. How I love them! How I need them! I'll have a long beard by the time I read them”—Lobel, Arnold. Whiskers and Rhymes. William Morrow & Co, 1988.

EU Careers info

Your career in the European Union

Carlos Sierra's Tools and Tips

Tools and Tips for Oracle Performance and SQL Tuning

Oracle Scratchpad

Just another Oracle weblog

OraStory

Dominic Brooks on Oracle Performance, Tuning, Data Quality & Sensible Design ... (Now with added Sets Appeal)

%d bloggers like this: