Skip to Main Content
  • Questions
  • Poor performance in call PLSQL Stored from Java

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Alessandro.

Asked: February 17, 2001 - 4:23 am UTC

Last updated: August 17, 2001 - 12:40 pm UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Hi,
I am converting a huge java application from SQL Server 7 to Oracle 8.1.6. All interface to data was written with 300 very simple TSQL Stored Procedure like select * from ..... where.. etc where a ResultSet (cursor is returned).
My Oracle DBA converted all Stored Procedure but all application the procedure is very slow.
Afer 2 days of profilng I understand :
ALL call to Stored procedure from Java to Oracle is 2 times more slow than original SQL Server.
I use ORACLE JDBC 8.1.7 driver with Callable statement.

The DB performance is the same : table are right indexes.

1000 calls to a simple TSQL Stored procedure like this

select * from address where ID = 1001

take 1612 milliseconds in SQL 7
and in Oracle for the same thing the result is 3101 !!!!

After profling the real problem is not the data on DB (the stored lanchued from PL/SQL takes 0.0 seconds) but is the call from Java to Oracle (I see tons of questions about it but nothing REAL answer). This is confirmed because if i call the PL/SQl procedure 1000 times from PL/SQl the time is 0.2 !!!!!!Now for me is very hard rewrite all access to data and I Think that this is a good choiche (pattern MVC) to divide access to view data.

All this code is used in 137 JSP pages on our site.
Is very difficult (or impossible) to obtain all data in a single call. ALL texts in the pages are stored in DB and all data too.

Now the SQL Stored Procedure (in the GET package) :

PROCEDURE word1_b(IDWord1 IN NUMBER,str OUT varchar2) IS
begin
select word into str from Words where ID=IDWord1 and rownum <= 1 order by IDLanguage ;
exception when no_data_found then str:=null;
end;

PROCEDURE wordb(IDWord1 IN NUMBER,IDLang1 IN NUMBER,str1 OUT varchar2) IS
begin
select /* INDEX(WORDS,XAK1WORDS) */ word into str1 from Words where ID=IDWord1 AND IDLanguage=IDLang1;
exception when no_data_found then str1:=null;
end;


and the Java code (this routine is used for profiling ):

synchronized String getWordb(int ID,int IDLang,Connection conn) throws SQLException {
CallableStatement stmt = null;
ResultSet rs = null;
String word="";
stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");
stmt.setInt(1,ID);
stmt.setInt(2,IDLang);
stmt.registerOutParameter (3, java.sql.Types.VARCHAR);
stmt.execute();
word = stmt.getString (3);
if (word == null) {
stmt=conn.prepareCall("{ call get.word1_b (?,?)}");
stmt.setInt(1,ID);
stmt.registerOutParameter (2, java.sql.Types.VARCHAR);
stmt.execute();
word = stmt.getString (2);
}
stmt.close();
return word;
}


and Tom said...

Ok, first -- not all databases are the same. What is a best practice in one is not in the other. The above is definitely not a "best practice" in Oracle...

I myself would have coded that much differently -- it would definitely have been 1 stored procedure, not two chained together by the client over the network.


I definitely would optimize this code as follows (without changing the database and without changing the way it behaves on anyones database):

CallableStatement stmt;
CallableStatement stmt2;

synchronized String getWordb(int ID,int IDLang,Connection conn)
throws SQLException
{
String word="";
if ( stmt = null )
stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");
stmt.setInt(1,ID);
stmt.setInt(2,IDLang);
stmt.registerOutParameter (3, java.sql.Types.VARCHAR);
stmt.execute();
word = stmt.getString (3);
if (word == null)
{
if ( stmt2 = null )
stmt=conn.prepareCall("{ call get.word1_b (?,?)}");

stmt2.setInt(1,ID);
stmt2.registerOutParameter (2,java.sql.Types.VARCHAR);
stmt2.execute();
word = stmt2.getString (2);
}
return word;
}

at a minimum that is what I would do. preparing a statement before each and every call is the very worst thing you can do to us. You are doing what is called a soft parse before each and every call to the database. You might just as well compile each class method before any invocation as well -- the effect is pretty much the same. You wouldn't dream of invoking javac and dynamically loading a class every time to invoke a method -- you are effectively doing that to us by preparing a call each and every time.

even more so -- the above database logic really belonged in the database. You could fix this immediatly by using a single statement that does:

declare
w varchar2(255);
begin
get.wordb( ?,?, w );
if ( w is null ) then
get_wrod1_b( ?, w );
end;
? := w;
end;

or creating a stored procedure really_get_word() that does the above logic.

Just to show you the difference between "compiling a statment and executing" and "compile it once, execute it many" I wrote a small java program. The results are:

> java test
JDBC driver version is 8.1.6.2.0
2001-02-19 09:02:14.46: Going to bad
2001-02-19 09:02:16.932: Going to good
2001-02-19 09:02:17.992: done

2.472 seconds the "bad" way.
1.060 seconds the "good" way.


the test code was:

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class test
{


public static void showTimeStamp( String msg )
{
System.out.println( (new
Timestamp(System.currentTimeMillis())).toString() +
":" + msg );
}

static CallableStatement cstmt_persistent;

public static void good_way(Connection conn ) throws Exception
{
if ( cstmt_persistent == null )
{
cstmt_persistent =
conn.prepareCall("{ call proc2( ?, ? ) }" );
cstmt_persistent.registerOutParameter
( 2, java.sql.Types.VARCHAR );
}
cstmt_persistent.setInt(1, 1);
cstmt_persistent.execute();
String w = cstmt_persistent.getString(2);
}

public static void bad_way(Connection conn ) throws Exception
{
CallableStatement cstmt =
conn.prepareCall("{ call proc1( ?, ? ) }" );

cstmt.setInt(1, 1);
cstmt.registerOutParameter( 2, java.sql.Types.VARCHAR );

cstmt.execute();
String w = cstmt.getString(2);
cstmt.close();
}

public static void main(String args[])throws Exception
{
DriverManager.registerDriver
(new oracle.jdbc.driver.OracleDriver());

Connection conn=DriverManager.getConnection
//("jdbc:oracle:thin:@aria-dev:1521:ora816dev"
("jdbc:oracle:oci8:@ora816dev.us.oracle.com"
,"scott","tiger");

DatabaseMetaData meta=conn.getMetaData();
System.out.println
("JDBC driver version is "+meta.getDriverVersion());

conn.setAutoCommit(false);

// stmt.execute( "alter session set sql_trace=true" );

showTimeStamp( "Going to bad" );
for( int i = 0; i < 1000; i++ ) bad_way(conn);

showTimeStamp( "Going to good" );
for( int i = 0; i < 1000; i++ ) good_way(conn);

showTimeStamp( "done" );
}
}

proc1 and proc2 were simply:


scott@DEV816> create or replace procedure proc1( x in int, y out varchar2 )
2 as
3 begin
4 y:='hello';
5 end;
6 /

Procedure created.
scott@DEV816> create or replace procedure proc2( x in int, y out varchar2 )
2 as
3 begin
4 y:='hello';
5 end;
6 /

Procedure created.





Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Good answer, critical for anyone wanting to scale

Harrison, June 04, 2001 - 1:56 pm UTC

For anyone who has not seen the almost identical discussion
of bind variables, this a good primer. There are so many
tools (prepared statements, callable statements) that speed
things up that you have to wonder why every class on
programming doesn't start with a short list of "Don't
do this", kinda like they do in soccer:"Don't shoot into
own goal".

Informative but has a compile time error

Franz Holzinger, August 17, 2001 - 10:33 am UTC

There's an Error in the following statement:
if ( stmt = null )
stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");

It must be:

if ( stmt == null )
stmt = conn.prepareCall("{ call get.wordb (?,?,?)}");




Tom Kyte
August 17, 2001 - 12:40 pm UTC

absolutely correct - sorry about that, it needs to be == not just =

Julius, July 22, 2005 - 5:39 pm UTC

Tom,

with a slightly modified version of the above example, I always get the same parse count in the "good" version as in the "bad" version. Any idea what I'm doing wrong?

Thanks!

julius:> java TKTest
JDBC driver version is 9.2.0.3.0
2005-07-22 14:06:56.685:Going to good
2005-07-22 14:07:15.516:Going to bad
2005-07-22 14:07:28.255:done
julius:>

> BEGIN tk.proc1(:1,:2) ; END;
>
>
> call count cpu elapsed disk query current rows
> ------- ------ -------- ---------- ---------- ---------- ---------- ----------
> Parse 1000 0.56 1.86 0 0 0 0
> Execute 1000 0.80 3.58 0 0 0 1000
> Fetch 0 0.00 0.00 0 0 0 0
> ------- ------ -------- ---------- ---------- ---------- ---------- ----------
> total 2000 1.36 5.44 0 0 0 1000
>
> Misses in library cache during parse: 1
> Optimizer goal: CHOOSE
> Parsing user id: 152

> BEGIN tk.proc2(:1,:2) ; END;
>
>
> call count cpu elapsed disk query current rows
> ------- ------ -------- ---------- ---------- ---------- ---------- ----------
> Parse 1000 0.42 0.28 0 0 0 0
> Execute 1000 0.94 1.17 0 0 0 1000
> Fetch 0 0.00 0.00 0 0 0 0
> ------- ------ -------- ---------- ---------- ---------- ---------- ----------
> total 2000 1.36 1.46 0 0 0 1000
>
> Misses in library cache during parse: 1
> Optimizer goal: CHOOSE
> Parsing user id: 152


create or replace package tk
as

type resultSet is ref cursor;

procedure proc1( x in int, y out resultSet );
procedure proc2( x in int, y out resultSet );
end;
/

create or replace package body tk
as

procedure proc1( x in int, y out resultSet )
is
begin
open y for select * from dual;
end;

procedure proc2( x in int, y out resultSet )
is
begin
open y for select * from dual;
end;

end;
/

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;

class TKTest
{


public static void showTimeStamp( String msg )
{
System.out.println( (new
Timestamp(System.currentTimeMillis())).toString() + ":" + msg );
}

static CallableStatement cstmt_persistent;
static ResultSet rs;

public static void good_way(Connection conn ) throws Exception
{
if ( cstmt_persistent == null )
{
cstmt_persistent = conn.prepareCall("{ call tk.proc1(?,?) }" );
cstmt_persistent.registerOutParameter( 2,OracleTypes.CURSOR );
}
cstmt_persistent.setInt(1,1);
cstmt_persistent.execute();
rs = (OracleResultSet)cstmt_persistent.getObject(2);
while (rs.next())
{
;
}
rs.close();
}

public static void bad_way(Connection conn ) throws Exception
{
CallableStatement cstmt =
conn.prepareCall("{ call tk.proc2(?,?) }" );

cstmt.setInt(1, 1);
cstmt.registerOutParameter( 2, OracleTypes.CURSOR );

cstmt.execute();
rs = (OracleResultSet)cstmt.getObject(2);
while (rs.next())
{
;
}
rs.close();

cstmt.close();
}

public static void main(String args[])throws Exception
{
DriverManager.registerDriver
(new oracle.jdbc.driver.OracleDriver());

Connection conn=DriverManager.getConnection
("jdbc:oracle:oci8:@dev1","scott","tiger");

DatabaseMetaData meta=conn.getMetaData();
System.out.println ("JDBC driver version is "+meta.getDriverVersion());

conn.setAutoCommit(false);

Statement stmt = conn.createStatement ();
stmt.execute( "alter session set sql_trace=true" );

showTimeStamp( "Going to good" );
for( int i = 0; i < 1000; i++ ) good_way(conn);

showTimeStamp( "Going to bad" );
for( int i = 0; i < 1000; i++ ) bad_way(conn);

showTimeStamp( "done" );
}
}


More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library