Frederick Tang Weblog

Stories about Oracle concepts I have learnt from work, and the occasional brain-dump…

Archive for the ‘9i’ Category

sys_connect_by_path and ORA-01489

without comments

This is strictly on an issue on a Oracle9i database. It has been fixed in Oracle10g.

Just would like to write a brief note about a test case we found with using sys_connect_by_path to concatenate values on a column from multiple rows into one string, on an Oracle9i database.

This technique has been discussed on a few websites:
http://www.williamrobertson.net/documents/one-row.html
http://www.oracle-base.com/articles/10g/StringAggregationTechniques.php
http://www.oracle.com/technology/oramag/code/tips2006/101606.html

As we might all know, VARCHAR2 in Oracle 9i has a limit of 4000 bytes (note, not characters). This limits applies to string concatenation, and will (or should) result in ORA-01489: result of string concatenation is too long if the result of concatenation yields a string greater than 4000 bytes in length. Eddie has demonstrated some examples here: http://awads.net/wp/2005/12/06/more-on-generating-strings/

I discovered a difference in behaviour when concatenating strings using sys_connect_by_path on Oracle9i, between a Multibyte character set database and a Single-byte character set database, where I would get an ORA-01489 error at just over the 2000 bytes mark on a Multibyte database. For example, UTF8 is Multibyte and WE8ISO8859P1 is a Single-byte character set. (http://download.oracle.com/docs/cd/B10501_01/server.920/a96529/appa.htm#956722)

There are a couple of ways to find out which character set the database is built on (requires dba privilege or select_catalog_role granted):

select value$ from props$ where name='NLS_CHARACTERSET';
select * from database_properties where property_name='NLS_CHARACTERSET';

We construct a simple test case, a table with an ID column, and a VARCHAR2 column. I am going to insert a fixed string of 10 characters into the VARCHAR2 column, then apply the string concatenation technique using sys_connect_by_path. I am not using any special characters, so each character should just be 1 byte. It is possible to get size of a varchar2 string using vsize().

create table test_concat_sys
(
   id     number,
   str    varchar2(4000)
);

insert into test_concat_sys
SELECT 1 as id, '1111111111' RNDMSTR
FROM all_objects
WHERE rownum <= 200;

commit;

On a UTF8 character set database, this will work, and the last row will be 2002 bytes (182×10+182). That’s 182 rows of 10 bytes, plus 1 byte for each ‘/’ in the concatenation delimiter.

SELECT VSIZE(SYS_CONNECT_BY_PATH(str, '/')) as BYTES
FROM
(
   select A.*, row_number() over (partition by id order by id) row#
   from test_concat_sys A
   where rownum <= 182
)
START WITH row#=1
CONNECT BY PRIOR id=id and prior row# = row# -1;

This will result in an ORA-01489 error:

SELECT VSIZE(SYS_CONNECT_BY_PATH(str, '/')) as BYTES
FROM
(
   select A.*, row_number() over (partition by id order by id) row#
   from test_concat_sys A
   where rownum <= 183
)
START WITH row#=1
CONNECT BY PRIOR id=id and prior row# = row# -1;

The behavior is working as expected on a Oracle 9i Single-byte database or an Oracle10g (or later) release, and we can set rownum upto 363 rows (363×10+363 = 3993), one more row and it will return ORA-01489 as expected since we will reach the limit (364×10+364 = 4004).

I have logged a SR for this, and the reply from Oracle was that they “officially accepted this is the right candidate for bug.”, but “9i is in sustaining support mode and development will not provide any fix for this. Also this issue is fixed in 10g so they would not accept it.”

All the more reason to upgrade to 10g if not already done so. This bug is rather odd to hit because we wouldn’t normally expect to concatenate a string upto 2000 characters and beyond. But in our case, the business obviously did.

There are other ways to concatenate strings from multiple rows, they are also covered in the websites linked at the beginning of this article.

Written by fredericktang

October 21, 2009 at 1:38 am

Posted in 9i, Oracle

Client side connect time load balancing for RAC

without comments

Recently, I took an interest in studying how various load balancing features work in a 10g RAC. I thought Jim has written a very good article on this, in a language most people can understand. In my opinion, there are 3 types of load balancing on a 10g RAC. I say in my opinion because Run Time Load Balancing relies on Server Side Load Balancing configurations, but I have separated it out anyways.

  1. Client Side Load Balancing
  2. Server Side Load Balancing
  3. Run Time Load Balancing (using Implicit Connection Cache)

In this post, I wanted to test for evidence of Client Side Load Balancing, using Client Side Oracle Net Tracing.

Definition

Client Side Load Balancing is by definition “evenly spreads new connection requests across all listeners”, and “Oracle Database randomly selects an address in the address list and connects to that node’s listener. This provides a balancing of client connections across the available listeners in the cluster.” [source]. Another definition, “whereby if more than one listener services a single database, a client can randomly choose between the listeners for its connect requests. This randomization enables all listeners to share the burden of servicing incoming connect requests.” [source].

Client Side Load Balancing is configured by adding LOAD_BALANCE=ON in tnsnames.ora file. The Net Services Reference tells us how and were to embed this parameter – “embed this parameter under either the DESCRIPTION_LIST parameter, the DESCRIPTION parameter, or the ADDRESS_LIST parameter.” [source]. Note however, whether you place LOAD_BALANCE under ADDRESS_LIST or outside, will make a different in terms of load balancing ADDRESS_LISTs or load balancing ADDRESSes.

Configuration

I have a 2-node 10g RAC cluster, and for load balancing, I have the following entry in my tnsnames.ora file.

MODDB=
 (description=
  (load_balance=on)
   (address=(protocol=tcp)(host=moddb1-vip)(port=2521))
   (address=(protocol=tcp)(host=moddb2-vip)(port=2521))
  (connect_data=
      (service_name=MODDB)
  )
 )

I want to trace the Client Side Load Balancing behaviour from the client side, so I enable Oracle net tracing by adding the following lines in sqlnet.ora [source], and I should fine trace files for each physical connection made under $ORACLE_HOME/network/trace.

TRACE_LEVEL_CLIENT = USER
TRACE_FILE_CLIENT = SQLTRC

Testing a connection

We can first test one connection…

$ sqlplus -s tester/tester@MODDB

exit

Now take a look at the trace file produced, under $ORACLE_HOME/network/trace. An inspection of of the trace file shows two entries that are of interest (it is possible to observe server-side load balancing as well, but that’s a topic for another post):

niotns: Calling address: (description=(load_balance=on)(failover=on)(address=(protocol=tcp)(host=moddb1-vip)(port=2521))(address=(protocol=tcp)(host=moddb2-vip)(port=2521))(connect_data=(service_name=MODDB)(CID=(PROGRAM=e:\oracle\product\10.2.0\client_1\bin\sqlplus.exe)(HOST=LADDO)(USER=tester))))
nsc2addr: (DESCRIPTION=(load_balance=on)(failover=on)(address=(protocol=tcp)(host=moddb2-vip)(port=2521))(connect_data=(service_name=MODDB)(CID=(PROGRAM=e:\oracle\product\10.2.0\client_1\bin\sqlplus.exe)(HOST=LADDO)(USER=tester))))

Unfortunately, I couldn’t find much documentation on how to read a trace file, I can only guess…

* niotns entry shows a lookup of tns information based on the service name I supplied “MODDB”.
* nsc2addr entry shows Oracle Net connects to listener on moddb2-vip.

It’s difficult to observe load balancing with just one connection, so I wrote a script to run 1000 connections, using Bash Shell scripting:

#!/bin/bash

for a in {1..1000}
do
echo $a
sqlplus -s tester/tester@MODDB<<EOF
EOF
done

exit 0

Examining the traces

Extending the idea above, we can use a combination of grep, and wc to see the result of load balancing. For some reason, each connection produced 2 trace files, so we have 2000 trace files for 1000 connections.

$ ls -l *.trc |wc -l
2000

$ grep nsc2addr *.trc | grep load_balance |grep moddb1-vip |wc -l
498

$ grep nsc2addr *.trc | grep load_balance |grep moddb2-vip |wc -l
502

We can see over 1000 connections, 498 were made to moddb1-vip listener, whilst 502 were made to moddb2-vip listener. It is a fairly even distribution. But just how random is it? I have used the output of the trace files, and plot the first 100 connections on a scattered chart. The first note one can observe is that, these connections are not in a round-robin fashion. (1 = moddb1-vip; 2 = moddb2-vip)

timeplot

Conclusion

As demonstrated above, it is quite simple to setup Oracle Net tracing on the client, to test and show whether Client Side Load Balancing is working properly. It is worth to note (again), that this type of load balancing has nothing to do with balancing server side load. It is to do with balancing load across the listeners.

Written by fredericktang

March 13, 2009 at 5:26 am

Posted in 10g, 9i, Oracle

PL/SQL Instrumentation using ILO

with 2 comments

I worked on a project to develop a domain index using the Oracle Data Cartridge Interface last year, When I was testing my code, I needed to know what the progress and state of the PL/SQL execution, and found dbms_output.put_line() inadequate as it only outputs when the call is returned to SQL*Plus, i.e. the output is not displayed during run-time. This is better explained by Robert Vollman. That was until Douglas showed me how to use DBMS_APPLICATION_INFO, MODULE, ACTION and CLIENT_INFO.

Whilst reading blogs of my favourite Oracle bloggers, I found Instrumentation Library for Oracle (ILO) developed by Method-R, presented here by Karen Morton. Instrumentation is described by Tom Kyte in this blog post. The idea behind ILO is quite interesting, a comment by Cary Millsap reveals the motivation behind this tool. I downloaded ILO 2.2, installed it on my 10g test database and test ran a very simple test case. I will write a few words about what I found.

I start off with a very simple test code, I granted myself privilege to execute dbms_lock:

DECLARE
BEGIN
   -- Start Task 1
   ILO_TASK.BEGIN_TASK(module=>'ILO Tester', action=>'Task 1', comment=>'Time 0s', begin_time=>sysdate);
   dbms_lock.sleep(10);
   -- Start Task 2
   ILO_TASK.BEGIN_TASK(module=>'ILO Tester', action=>'Task 2', comment=>'Time 10s',begin_time=>sysdate);
   dbms_lock.sleep(10);

   -- End Task 1
   ILO_TASK.END_TASK(end_time=>sysdate);
   -- End Task 2
   ILO_TASK.END_TASK(end_time=>sysdate);
EXCEPTION
   WHEN OTHERS
   THEN
      dbms_output.put_line('Exception thrown');
      ILO_TASK.END_ALL_TASKS(p_error_num=>SQLCODE, p_end_time=>sysdate);
   END;

I am logged on the database using SQL*Plus, before I execute the above PL/SQL block, my session information looks like this:

SQL> select module, action, client_info from v$session where username='FREDT';

MODULE          ACTION          CLIENT_INFO
--------------- --------------- ---------------
SQL*Plus

When the PL/SQL block is executed:

MODULE          ACTION          CLIENT_INFO
--------------- --------------- ---------------
ILO Tester      Task 1

10 seconds later inside the PL/SQL block:

MODULE          ACTION          CLIENT_INFO
--------------- --------------- ---------------
ILO Tester      Task 2

10 seconds more, and PL/SQL block exits:

MODULE          ACTION          CLIENT_INFO
--------------- --------------- ---------------
SQL*Plus

ILO manages tasks in a stack like data structure, therefore, a developer can start multiple tasks using BEGIN_TASK and end the last (started) task using END_TASK, or end all tasks using END_ALL_TASKS. To view session information, one needs either DBA or SELECT ANY DICTIONARY privilege.

This most basic use of ILO requires continuous query on v$session to get anything meaningful. If one wants to take this further, ILO provides the framework to do so. For example, one can enable tracing by inserting this line of code at the beginning of the PL/SQL block (after the line BEGIN):

ILO_TIMER.set_mark_all_tasks_interesting(true, true);

A trace file will be produced in USER_DUMP_DEST, and one can find ILO information inside the trace file. It seems feasible to develop a script which can extract this information out of a trace file into something meaningful or simply more human-readable:

ILO_TASK.BEGIN_TASK[][ILO Tester][Task 1][oracle~pts/2~sqlplus@moddb1 (TNS V1-V3)~SYS$USERS][Time 0s]
*** 2009-01-13 16:50:02.919
*** ACTION NAME: (Task 1) 2009-01-13 16:50:02.919
*** MODULE NAME: (ILO Tester) 2009-01-13 16:50:02.919

ILO_TASK.BEGIN_TASK[][ILO Tester][Task 2][oracle~pts/2~sqlplus@moddb1 (TNS V1-V3)~SYS$USERS][Time 10s]
*** 2009-01-13 16:50:12.921
*** ACTION NAME: (Task 2) 2009-01-13 16:50:12.921
*** MODULE NAME: (ILO Tester) 2009-01-13 16:50:12.921

ILO_TASK.END_TASK[][ILO Tester][Task 2][oracle~pts/2~sqlplus@moddb1 (TNS V1-V3)~SYS$USERS][Time 10s][]
*** ACTION NAME: (Task 1) 2009-01-13 16:50:22.931
*** MODULE NAME: (ILO Tester) 2009-01-13 16:50:22.931

ILO_TASK.END_TASK[][ILO Tester][Task 1][oracle~pts/2~sqlplus@moddb1 (TNS V1-V3)~SYS$USERS][Time 0s][]
*** ACTION NAME: () 2009-01-13 16:50:22.931
*** MODULE NAME: (SQL*Plus) 2009-01-13 16:50:22.931

After reading the ILO source code, and playing around, I could think of a few more ways to take this idea further, but it will require additional development of the library. Most of the “hooks” are inside ILO_TIMER. Indeed, I believe that is why ILO is licensed as LGPL, allowing developers the flexibility to decide how they want to make use of the instrumentation.

Perhaps I will write about these ideas in another post. My parting thoughts were how to have Developers instrument their code the same way, if I am the DBA. Perhaps, if DBA=Developer then it would be a different story. I think about Apache Log4j and if the same adoption can be applied to a tool like ILO, that would be great.

Written by fredericktang

January 13, 2009 at 7:11 am

Posted in 10g, 9i, Oracle

Foreign keys and locking

without comments

Sometimes things are learnt the hard way. A couple of weeks ago, I was involved in purging some historical data from a production database using a set of DELETE statements. I assumed these transactions will not impact the Application (performance aside) because of how Oracle maintains read consistency and concurrency. This assumption was formed on weak knowledge on my part …

In short, the DELETE statements were executed on a parent table. It has foreign key on delete cascade relationships with several child tables. There were no indexes defined on the foreign key columns of the child tables. Whilst the DELETE statements were executing, the child tables became locked against other DMLs until a COMMIT or ROLLBACK is issued. Other DML transactions on these unindexed child tables appeared to hang, i.e. enqueued.

The on delete cascade constraint was the key, if it was disabled, locking would not happen but instead the transaction will fail with ORA-02292: constraint violation – child records found. However, enabling the on delete cascade constraint wass the right thing to do. To prevent locking, we just had to define indexes on the foreign key columns.

This apparent database design knowledge showed how little I know about Oracle, and how inexperienced I am.

I conjured a more simplistic example, and also found some reference to this in Metalink.

(Sorry… I am just being very silly with the example…)

Create tables:

Suppose I create two parent tables, and a child table with foreign keys linked to them. The foreign keys do not have indexes.

create table dad_table
(
   id number(10),
   name varchar2(10),
   constraint pk_dad_table_id primary key (id)
);

create table mum_table
(
   id number(10),
   name varchar2(10),
   constraint pk_mum_table_id primary key (id)
);

create table child_table
(
   dad number(10)
       constraint fk_child_dad_table_id
       references dad_table(id)
       on delete cascade,
   mum number(10)
       constraint fk_child_mum_table_id
       references mum_table(id)
       on delete cascade,
   name varchar2(10)
);

SQL> select index_name from user_indexes
  2  where table_name='CHILD_TABLE';

no rows selected



Populate data:

insert into dad_table (id, name) values (1, 'john');
insert into dad_table (id, name) values (3, 'peter');
insert into dad_table (id, name) values (5, 'david');

insert into mum_table (id, name) values (2, 'mary');
insert into mum_table (id, name) values (4, 'jane');
insert into mum_table (id, name) values (6, 'ivy');

insert into child_table (dad, mum, name) values (1, 2, 'ashton');
insert into child_table (dad, mum, name) values (3, 4, 'james');



Testing:

I am going to start three Oracle sessions, two as the schema user, one as SYSDBA which I will use to monitor locks. I used the locks information script from Metalink Note 1020012.6. I also found a script in Tom Kyte’s book (Expert Oracle Database Architecture – Chapter 6 – Locking and Latching) useful in displaying locks blocker and blockee.

i. Without indexes on foreign keys:

Session #1 (SID=11):

SQL> delete from dad_table where id=1;

1 row deleted.

Session #2 (SID=10):

SQL> delete from mum_table where id=4;

(... hangs)

Session SYSDBA:

* Note 1020012.6 -

 SID SERIAL# USERNAME   Resource Type        TAB        Lock Held      Lock Req.
---- ------- ---------- -------------------- ---------- -------------- --------------
  10    5596 FREDT      TM - DML Enqueue     DAD_TABLE  Row Share
  10    5596 FREDT      TM - DML Enqueue     MUM_TABLE  Row Exclusive
  10    5596 FREDT      TM - DML Enqueue     CHILD_TABL                Shr Row Excl
  11    3311 FREDT      TM - DML Enqueue     DAD_TABLE  Row Exclusive
  11    3311 FREDT      TM - DML Enqueue     MUM_TABLE  Row Share
  11    3311 FREDT      TM - DML Enqueue     CHILD_TABL Row Exclusive

* Tom's Blocker, Blockee and Object Name -

BLOCKER       SID BLOCKING        BLOCKEE       SID ON_OBJ OBJECT_NAME
---------- ------ --------------- ---------- ------ ------ --------------------
CIMS           11 is blocking     CIMS           10 on     CHILD_TABLE

* V$Session_Wait -

SQL> select sid, event from v$session_wait where event='enqueue';

SID EVENT
---- ----------------------------------------------------------------
10 enqueue


Comments:

The two sessions were deleting different rows of data, but one was blocking the other. The lock information showed SID 10 was requesting a Share Row Exclusive lock (SSX) on the child_table, when a Row Exclusive (SX) lock on the same table was held by SID 11. This conflict is explained in 10g Concept Guide (the 9i Concept Guide html formatting looks a bit out of whack).

The puzzling thing is why request a SSX lock? Metalink Note 33453.1 explains this behaviour, but it still does not explain the *why*:

Without indexes… A share lock (LMODE=4) of the entire child table is required until the transaction containing the insert/delete/update statement for the parent table is committed, thus preventing any modifications to the child table. It even can be a SSX (LMODE=5) lock when deleting from the parent table with a delete cascade constraint. In 9.2.0, the downgraded ‘mode 3 Row-X (SX)’ locks are no longer required except when deleting from a parent table with a ‘delete cascade’ constraint.

Another interesting point – SID 11 also acquired a Row Share (SS) lock on mum_table. It isn’t immediately obvious, but a SS lock (only) prohibits other transactions from acquiring an Exclusive (X) lock on table, either explicitly (lock table … in exclusive mode) or implicitly (alter table). Note 223303.1 also made this statement:

Starting in version 9.2.0, a Row-SS lock is taken on the parent table for any DML issued against the child table. This will occur with or without an index on the foreign key column of the child table.

The table structure is somewhat similar to the application schema I was working on. Whilst I was deleting from the parent table, the Application was also deleting rows from another parent table, to which both parent tables are linked to a single child table. Given the above experiment, I now understand why the Application appeared to have “hang” and threads were reporting timeout errors.

ii. With indexes on foreign keys:

Let’s first create two indexes on the child table:

create index ix_dad_child on child_table(dad);
create index ix_mum_child on child_table(mum);

Session #1 (SID=11):

SQL> delete from dad_table where id=1;

1 row deleted.

Session #2 (SID=10):

SQL> delete from mum_table where id=4;

1 row deleted.

Session SYSDBA:

* Note 1020012.6 -

 SID SERIAL# USERNAME   Resource Type        TAB        Lock Held      Lock Req.
---- ------- ---------- -------------------- ---------- -------------- --------------
  10    5596 FREDT      TM - DML Enqueue     DAD_TABLE  Row Share
  10    5596 FREDT      TM - DML Enqueue     MUM_TABLE  Row Exclusive
  10    5596 FREDT      TM - DML Enqueue     CHILD_TABL Row Exclusive
  11    3311 FREDT      TM - DML Enqueue     DAD_TABLE  Row Exclusive
  11    3311 FREDT      TM - DML Enqueue     MUM_TABLE  Row Share
  11    3311 FREDT      TM - DML Enqueue     CHILD_TABL Row Exclusive  

* Tom's Blocker, Blockee and Object Name -

no rows selected

* V$Session_Wait -

SQL> select sid, event from v$session_wait where event='enqueue';

no rows selected


Comments:

The two sessions are no longer in blocker-blockee state, and each can delete their different rows. The indexes defined on the foreign key columns have resolved the locking scenario introduced in this example.

Note 33453.1 explains this:

An Insert/Delete/Update on the parent table will only acquire a row level lock on the parent table if there is an index on the foreign key of the child table. The child table will have NO locks on it and so any type of modifications can be made to the child table…

If the child table specifies ON DELETE CASCADE, waiting and locking rules are the same as if you deleted from the child table after performing the delete from the parent. In 9.2.0 onwards, Oracle requires ‘mode 2 Row-S (SS)’ locks on the child table.

Metalink References:
Note:33453.1 – REFERENTIAL INTEGRITY AND LOCKING.
Note:16428.1 – Prevent Deadlock in Existing Parent- Child Tables By INDEXING PK and FK.
Note:223303.1 – Correction to 9.2.0 foreign key constraint locking behaviour, per documentation.
Note:29787.1 – VIEW: “V$LOCK” Reference Note.
Note:11828.1 – FOREIGN KEYS, INDEXES AND PARENT TABLE LOCKING.

Written by fredericktang

October 10, 2008 at 5:02 am

Posted in 9i, Oracle

CTAS via database link with timestamp column changes precision to 0

without comments

Found an interesting Oracle9i bug today, and it is easily reproduced (I am using 9.2.0.8 below). The bug is fixed in 10g (> 10.1.0.2).

SQL> create table timestamp_test (ts timestamp(5));

Table created.

SQL> desc timestamp_test;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TS                                                 TIMESTAMP(5)

SQL> drop database link dblink_to_self;

Database link dropped.

SQL> create database link dblink_to_self
  2     connect to fredt identified by fredt
  3     using 'FOOMDB';

Database link created.

SQL> create table timestamp_test_ctas
  2  as
  3  select * from timestamp_test@dblink_to_self;

Table created.

SQL> desc timestamp_test_ctas;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TS                                                 TIMESTAMP(5)

SQL> select table_name, data_type from user_tab_cols
  2  where table_name like 'TIMESTAMP%';

TABLE_NAME                     DATA_TYPE
------------------------------ ---------------
TIMESTAMP_TEST                 TIMESTAMP(5)
TIMESTAMP_TEST_CTAS            TIMESTAMP(0)


See Metalink Bug 2417643 for further reference.

Written by fredericktang

September 8, 2008 at 7:43 am

Posted in 9i, Oracle

Java, Oracle and CLOB

with one comment

Today, one of the developers encountered the following error when trying to insert more than 4000 bytes into a CLOB datatype column. The environment is Java 1.4 and Oracle9i (9.2.0.8) database:

ORA-01704: string literal too long

That can happen if the literal (the 4000+ characters String) is embedded into an INSERT statement like a normal String/VARCHAR2. Suppose I create a table like so:

SQL> create table clob_test (id number(10), str clob);

Table created.

This INSERT method won’t work, and will result in the Oracle error, this is because VARCHAR2 has a 4000 bytes limit:

// Create a String of 5000 characters
char[] charArray = new char[5000];
String clobstr = new String(charArray);
     
Statement stmt = conn.createStatement();
try {
   String sql = “INSERT INTO clob_test (str) values (‘”+clobstr+”‘)”;
   stmt.executeUpdate(sql);
} catch (SQLException t1) {
   t1.printStackTrace();
} finally {
   stmt.close();
   conn.commit();
}

Thanks to Holly, she’s worked out a few methods to do this, but they require the use of oracle.sql.CLOB. I will show one of the suggested ways here:

try {
   String sql = “INSERT INTO clob_test (id, str) values (1, empty_clob())”;
   Statement stmt = conn.createStatement();
   stmt.executeUpdate(sql);
   sql = “select str from clob_test where id=1 for update”;
   ResultSet rss=stmt.executeQuery(sql);

   if(rss.next()){
      oracle.sql.CLOB clob= (oracle.sql.CLOB)rss.getClob(“str”);
      clob.putString(1,clobstr);
      sql = “update clob_test set str=? where id=1″;
      PreparedStatement pstmt=conn.prepareStatement(sql);
      pstmt.setClob(1,clob);
      pstmt.executeUpdate();
      pstmt.close();
   }
} catch (SQLException t2) {
   t2.printStackTrace();
} finally {
   stmt.close();
   conn.commit();
}

The Official Oracle9i suggested to use OutputStream, which you can read about it here. It looks a bit more complicated, but a Java developer has to evaluate which of these methods is a better alternative.

There are some good news for Oracle10g databases, which has a much easier way of working with CLOBs. The full Java demo is available here.

Written by fredericktang

July 31, 2008 at 9:31 am

Posted in 10g, 9i, Java, Oracle

Size of NUMBER

with 4 comments

Sometimes curiosity gets me into researching something that may not be of use at times, or maybe I just don’t have the wisdom to realise its value.

The last few weeks, whilst I was researching into a problem at work, I found myself sidetracked into learning the size of NUMBERs stored in an Oracle database, and more specifically, how Oracle stores NUMBERs internally. The size of NUMBERs in Oracle database is not a fixed number like Java’s int – 4 bytes, it is variable depending on the value.

Before the story continues, this information is documented in the Concept Guide: Oracle10g and Oracle9i. Oracle may change its implementation with every release, therefore, it is best to check the documentation. On top of that, some Metalink Notes also goes into demonstrating how the internal representation works: 1007641.6 – Understanding Oracle NUMBER Datatype; 1031902.6 – How Does Oracle Store Internal Numeric Data. For more reading, Chapter 12 of Thomas Kyte’s Expert Oracle Database Architecture book provides more explanation.

A numeric column can be defined as NUMBER or NUMBER (p, s), where p = precision (total number of digits) and scale (number of digits to the right of the decimal point). “Each value is stored in scientific notation, with one byte used to store the exponent and up to 20 bytes to store the mantissa. Oracle does not store leading and trailing zeroes.” – Note1031902.6.

For example, if I declare a NUMBER(5,2) column, this means I can store values up to precision (p) = 5 and scale (s) =2. Since values are represented in scientific notation, the number 123.45 has p=5 and s=2. It is interesting to note the number 10000 will not fit in a NUMBER(5,2) column because 10000.00 has 7 digits in total when converted to scale of 2 – a similar example is explained in Tom’s book.

Interesting thing is, how the column is defined in the table does not have a direct relation with the size (bytes) of the NUMBER it stores. To demonstrate, I will create a table with a NUMBER(5,2) column and insert a few values, then use the dump() and vsize() to show the internal representation:

SQL> create table number_test (i number(5,2));

Table created.

SQL> insert into number_test (i) values (123.45);

1 row created.

SQL> insert into number_test (i) values (12.23);

1 row created.

SQL> insert into number_test (i) values (1);

1 row created.

SQL> insert into number_test (i) values (0.99);

1 row created.

SQL> insert into number_test (i) values (1234.12);
insert into number_test (i) values (1234.12)
*
ERROR at line 1:
ORA-01438: value larger than specified precision allows for this column

SQL> insert into number_test (i) values (100);

1 row created.

SQL> commit;

Commit complete.

SQL> col dump format a40

SQL> select i, dump(i) as dump, vsize(i) as vsize from number_test;

         I DUMP                             VSIZE(I)
---------- ------------------------------ ----------
    123.45 Typ=2 Len=4: 194,2,24,46                4
     12.23 Typ=2 Len=3: 193,13,24                  3
         1 Typ=2 Len=2: 193,2                      2
       .99 Typ=2 Len=2: 192,100                    2
       100 Typ=2 Len=2: 194,2                      2

Interesting results to observe from the above output, the dump() function showed the length of NUMBER values, as well as how the NUMBER is internally represented. For example, 123.45 is represented in 4 bytes and the value of each byte is 194, 2, 24, 46 respectively. How these four bytes represent the actual value is explained in Note 1007641.6.

Perhaps what us mere mortals are really after, is a simple formula that can calculate the size of the NUMBER values. This is given in the Oracle Concept Guide and Metalink Note in a slightly different form:

Oracle Concept Guide: ROUND((length(p)+s)/2))+1, where p = precision of the value, s = 0 if value is positive; s = 1 if value is negative.

Metalink Note 1031902.6: FLOOR[(p+1)/2] + 1 and add +1 byte (only for negative numbers where the number of significant digits is less than 38), where p = prevision of the value and scale has no effect.

The mind-boggling thing is these two formulas are actually the same, with a little interpretation required: length(p) actually means the number of significant digits without leading or trailing zeroes, and the p in the Metalink Note formula actually meant length(p). So following my examples above:

Number length(p) ROUND((length(p)+s)/2)+1 FLOOR((p+1)/2)+1
123.45 5 ROUND(5/2)+1 = 4 FLOOR((5+1)/2)+1 = 4
12.23 4 ROUND(4/2)+1 = 3 FLOOR((4+1)/2)+1 = 3
1 1 ROUND(1/2)+1 = 2 FLOOR((1+1)/2)+1 = 2
0.99 2 ROUND(2/2)+1 = 2 FLOOR((2+1)/2)+1 = 2
100 1 ROUND(1/2)+1 = 2 FLOOR((1+1)/2)+1 = 2



It is not hard to prove mathematically that ROUND(x/2)+1 = FLOOR((x+1)/2)+1, where x >= 0.

Written by fredericktang

July 10, 2008 at 7:53 am

Posted in 10g, 9i, Oracle

Failover for 9i RAC

with 3 comments

– Edit: I have updated the title of this post to include 9i. New failover features are available in 10g, so this article is a little out-of-date.

Over the week, my colleagues and I discussed application failover on a Oracle9i RAC database. I don’t know much about how RAC works, so I decided to do some research and post some notes here. Hopefully, this information can add to someone else’s researching into understanding the technology.

Disclaimer: What I am posting here isn’t something new, the information is freely available in Oracle documentation. If you find the information I post here is wrong, please kindly let me know. By all means, test the technology for yourselves. If you would like to ask a question, please feel free and I will try research a bit more to give an educated answer when I have time.

To start this off, I would imagine I have a 2-node Oracle9i RAC database and the simplest client tnsnames entry would look like this:

BLOGRAC =
  (DESCRIPTION =
     (LOAD_BALANCE = ON)
     (ADDRESS_LIST =
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc1)(PORT=1521))
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc2)(PORT=1521))
     )
     (CONNECT_DATA = (SERVICE_NAME=BLOGRAC))
   )    

The first failover feature is connect-time failover, where “a client connect request is forwarded to another listener if a listener is not responding”[source][source]. To enable this, the client tnsnames entry might look like this:

BLOGRAC =
  (DESCRIPTION =
     (LOAD_BALANCE = ON)
     (FAILOVER = ON)
     (ADDRESS_LIST =
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc1)(PORT=1521))
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc2)(PORT=1521))
     )
     (CONNECT_DATA = (SERVICE_NAME=BLOGRAC))
   )

The next failover feature is Transparent Application Failover (TAF), where “the failover and reestablishment of application-to-service connections. It enables client applications to automatically reconnect to the database if the connection fails, and optionally resume a SELECT statement that was in progress. This reconnect happens automatically from within the Oracle Call Interface (OCI) library.” [source].

TAF has several restrictions [source]:

  • All PL/SQL package states on the server are lost at failover
  • ALTER SESSION statements are lost
  • If failover occurs when a transaction is in progress, then each subsequent call causes an error message until the user issues an OCITransRollback call. Then Oracle issues an Oracle Call Interface (OCI) success message. Be sure to check this message to see if you must perform additional operations.
  • Oracle fails over the database connection and if TYPE=SELECT in the FAILOVER_MODE section of the service name description, Oracle also attempts to fail over the query
  • Continuing work on failed-over cursors can result in an error message

To enable this feature, the client tnsnames entry might look like this:

BLOGRAC =
  (DESCRIPTION =
     (LOAD_BALANCE = ON)
     (FAILOVER = ON)
     (ADDRESS_LIST =
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc1)(PORT=1521))
        (ADDRESS=(PROTOCOL=TCP)(HOST=blograc2)(PORT=1521))
     )
     (CONNECT_DATA =
        (SERVICE_NAME=BLOGRAC)
        (FAILOVER_MODE=
           (TYPE=select)
           (METHOD=basic))
     )
   )

The TYPE are session| select | none, and METHOD are basic | preconnect [source].

As stated above, TAF is only supported on JDBC OCI drivers and not on JDBC Thin drivers. I found a couple of Oracle Metalink Notes that re-iterates this, although they are updated for Oracle10g, they still apply to Oracle9i. I also found a note which explains how to write Java code to enable OCI TAF callbacks:

Note:297490.1 Is TAF (Transparent Application Failover) Supported On The Latest JDBC Thin Driver (10g)?

Note:465423.1 Can the JDBC Thin Driver Do Failover by Specifying FAILOVER_MODE?

Note:276225.1 JDBC Sample on How to Implement OCI TAF callbacks with DML and PL/SQL

This is an aside… I wanted to know what error will occur to a Java application [source] when there’s instance shutdown or failure. I have written a very simple Java program to test this, and it will query results from a big table I created on a Oracle9i RAC database, using the Oracle9i JDBC Thin Driver:

1. Oracle instance shutdown

Vendor Error Code: 1089
java.sql.SQLException: ORA-01089: immediate shutdown in progress – no operations are permitted
    at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134)
    at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:289)
    at oracle.jdbc.ttc7.Oclose.receive(Oclose.java:122)
    at oracle.jdbc.ttc7.TTC7Protocol.close(TTC7Protocol.java:683)
    at oracle.jdbc.driver.OracleStatement.close(OracleStatement.java:644)
    at TestDBOracle.main(TestDBOracle.java:49)

2. Oracle instance failure (by killing the pmon and smon processes) – yes, it came out with funny characters…

Vendor Error Code: 17410
java.sql.SQLException: �S���q��y����
    at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134)
    at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:179)
    at oracle.jdbc.dbaccess.DBError.check_error(DBError.java:1160)
    at oracle.jdbc.ttc7.MAREngine.unmarshalUB1(MAREngine.java:963)
    at oracle.jdbc.ttc7.MAREngine.unmarshalSB1(MAREngine.java:893)
    at oracle.jdbc.ttc7.Oclose.receive(Oclose.java:101)
    at oracle.jdbc.ttc7.TTC7Protocol.close(TTC7Protocol.java:683)
    at oracle.jdbc.driver.OracleStatement.close(OracleStatement.java:644)
    at TestDBOracle.main(TestDBOracle.java:49)

[Please note: If your application is getting a 17410 error, it doesn't always mean the database / instance died.]

Written by fredericktang

May 30, 2008 at 9:24 am

Posted in 9i, Oracle

ARC0: Media Recovery Disabled

without comments

Just a quick one… when I was creating a new database today, I encountered this message in the alert log after the database was created and instance restarted. The message repeated itself continuously to the point I felt the need to find out what is going on.

Turns out the instance started the archiver with the database created in NOARCHIVELOG mode:

1. My CREATE DATABASE statement did not include the keyword ARCHIVELOG or NOARCHIVELOG. The default is NOARCHIVELOG.
2. My initialization parameter had log_archive_start=true.

Oracle Metalink Note: 131119.1 explains more about this:

To verify:

SQL> select log_mode from v$database; /* LOG_MODE is NOARCHIVELOG */
SQL> show parameter log_archive_start; /* VALUE is TRUE */

I am not going to copy the fix on the Note here. To fix my problem, I set log_archive_start=false in the initialization parameters and restarted the instance. I wanted my new database to be in NOARCHIVELOG mode initially.

The log_archive_start parameter is deprecated in Oracle10g (10.1).

Written by fredericktang

May 26, 2008 at 8:27 am

Posted in 9i, Oracle

Counting rows in an empty table

without comments

This is of course nothing new, but I stumbled across this interesting scenario today. I was trying to import a large table ~ 14mil rows into a test database for testing purposes. Half way during the import, it failed with “ORA-1653: unable to extend table…”. I simply ran out of space in my tablespace.

What is interesting is when I count the rows in the table, even when import failed.

SQL> set autotrace on
SQL> set timi on
SQL> select count(*) from se_activity_log;

  COUNT(*)
----------
        0 

Elapsed: 00:00:21.21 

Execution Plan
----------------------------------------------------------
     0   SELECT STATEMENT Optimizer=CHOOSE (Cost=14654 Card=1)
1    0   SORT (AGGREGATE)
2    1     TABLE ACCESS (FULL) OF 'SE_ACTIVITY_LOG' (Cost=14654 Card=15100349) 

Statistics
----------------------------------------------------------          
    0  recursive calls          
    0  db block gets      
68253  consistent gets      
62033  physical reads          
    0  redo size        
  490  bytes sent via SQL*Net to client        
  655  bytes received via SQL*Net from client          
    2  SQL*Net roundtrips to/from client          
    0  sorts (memory)          
    0  sorts (disk)          
    1  rows processed

So… it took 20 seconds to tell me I don’t have any rows in my table. Why?

It probably has to do with the High Water Mark of the table. Even though the Oracle Import failed, the extents allocated to the table is not released. A full table scan will scan up to the High Water Mark [reference]. Let’s take a look at the High Water Mark:

VARIABLE total_blocks number;
VARIABLE total_bytes number;
VARIABLE unused_blocks number;
VARIABLE unused_bytes number;
VARIABLE last_used_extent_file_id number;
VARIABLE last_used_extent_block_id number;
VARIABLE last_used_block number; 

begin dbms_space.unused_space(user,'SE_ACTIVITY_LOG','TABLE', :total_blocks, :total_bytes, :unused_blocks, :unused_bytes, :last_used_extent_file_id, :last_used_extent_block_id, :last_used_block);
end;
/
print total_blocks total_bytes unused_blocks unused_bytes last_used_extent_file_id last_used_extent_block_id last_used_block; 

SQL>
TOTAL_BLOCKS
------------       
       68608 

TOTAL_BYTES
-----------  
  562036736 

UNUSED_BLOCKS
-------------            
            0 

UNUSED_BYTES
------------           
           0 

LAST_USED_EXTENT_FILE_ID
------------------------
                      11 

LAST_USED_EXTENT_BLOCK_ID
-------------------------
                   126344 

LAST_USED_BLOCK
---------------
           1024

My test database is built on a slow machine, so full table scanning 500MB data takes a while. There’s no point in freeing the space allocated, because it was a failed Oracle Import, I will have to extend the tablespace and retry the Import again.

 

Written by fredericktang

May 5, 2008 at 3:24 am

Posted in 9i, Oracle