http://asktom.oracle.com/pls/asktom/f?p=100:11:2253849508741645::::P11_QUESTION_ID:10128287191505
Bind Variables and Java
One of the recent questions on the website asktom.Oracle.com recently was this interesting one. It
was about programming in Java with JDBC. This discussion applies equally to Visual Basic
programmers using VB with ODBC as the concept of "Statements" and "PreparedStatements" exists in
ODBC in more or less the same fashion. It questioned the use of Statements versus a
PreparedStatement. When using Statements in JDBC - you must use the "string concatenation
approach". Using PreparedStatements allows you to use bind variables. The question was:
Tom -- Please briefly skim this link (link omitted for obvious reasons) which gives an excerpt for
JDBC performance. It says always use statements (no bind variables allowed) instead of
preparedstatements because they perform better without discussing the impact on the database, only
in terms of a single apps metrics. Is this accurate or is this information just extremely short
sighted with regards to overall db impact?
Well, that was easy - I gave them the proof from above - case closed. PreparedStatements with bind
variables are absolutely without question the only way to do it. Of course, later on, I got a
followup:
For a moment keep aside shared pool, hard parse and soft parse and talk about PreparedStatement and
Statement as they are the only way to execute statements from java. I wrote this benchmark code
that shows a Statement performs better than a PreparedStatement unless you execute the same
statement a whole lot of times. So, I reproduced the findings of the above link and prove that
Statements are better than PreparedStatements.
I had some issues with this one - they missed the point. They start with "for a moment keep aside
shared pool, hard parse and soft parse". Well, if we were to ignore those - we totally miss the
boat on this topic as they are the only things to consider. The facts are:
o Hard Parsing incurs many latches
o Latches are serialization devices
o Serialization is not a scalable thing
o Therefore as you add users, the system that uses Statements instead of PreparedStatements with
bind variables will fail.
I quite simply could not observe their request to put aside the shared pool and hard/soft parse.
They are the relevant topics - they must be considered. That was my initial response - but you
know, this bothered me so much, I had to explore it further. So, starting with their benchmark code
which simply inserted into a database table, I made it a multi-user benchmark to demonstrate the
fact that if you expand this simple, single user benchmark out to a real world example with
multiple users - you will see clearly what the issue is and why you need to avoid statements.
But, an interesting thing happened. I could not reproduce their findings that a Statement in JDBC
without bind variables versus a PreparedStatement using bind variables. When I ran their code - I
could, using my code - I found that a single statement executed using either of a Statement or
PreparedStatement took the same amount of time initially and if we executed the SQL over and over -
the PreparedStatement was always much faster. This conflicted with their observations totally.
So, I set out to find out why. We'll walk through this process here because it does cover two
interesting things:
o If for some reason your test isn't meeting your hypothesis - either your hypothesis is wrong or
your test is flawed
o The seemingly simple, convincing test can be the most misleading thing in the world
We'll walk through this simple benchmark now, starting with their original test case and working up
to the "real thing". It used a single table TESTXXXPERF which was created using the script
perftest.sql as follows:
scott@ORA920> drop table testxxxperf;
Table dropped.
scott@ORA920> create table testxxxperf
2 ( id number,
3 code varchar2(25),
4 descr varchar2(25),
5 insert_user varchar2(30),
6 insert_date date );
Table created.
scott@ORA920> exit
Then, the main java code was supplied. It consisted of three subroutines basically - a main that
connected to the database and then called a routine to insert into that table using statement and
then called a routine to do the same with prepared statements. The code piece by piece is:
import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;
public class perftest
{
public static void main (String arr[]) throws Exception
{
Connection con = null;
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
con = DriverManager.getConnection
("jdbc:oracle:thin:@aria-dev:1521:ora920", "scott", "tiger");
con.setAutoCommit(false);
Integer iters = new Integer(arr[0]);
doStatement (con, iters.intValue() );
doPreparedStatement(con, iters.intValue() );
con.commit();
con.close();
}
That is the main routine which simply connects to my Oracle 9iR2 instance as scott/tiger - disables
the autocommit JDBC uses by default and then invokes the subroutine to execute a Statement N times
and then a PreparedStatement N times. I set it up to allow us to pass "N" into the Java routine so
we can run multiple simulations. Next, we'll look at the doStatement routine:
static void doStatement(Connection con, int count)
throws Exception
{
long start = new Date().getTime();
Statement st = con.createStatement();
for (int i = 0; i < count; i++)
{
st.executeUpdate
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)" +
" values (" + i + ", 'ST - code" + i + "'" +
", 'St - descr" + i + "'" + ", user, sysdate ) ");
}
long end = new Date().getTime();
st.close();
con.commit();
System.out.println
("statement " + count + " times in " +
(end - start) + " milli seconds");
}
Very straight forward - it simply creates a statement object and then loops "count" times and
builds a unique - never before seen INSERT statement and executes it. It is somewhat scaled back
from reality in that it is not checking for quotes in strings and fixing them up - but we'll let
that go for now. Also note that it retrieves the time before and after executing the statement and
prints out the results. Next, we look at the prepared statement:
static void doPreparedStatement (Connection con, int count)
throws Exception
{
long start = new Date().getTime();
PreparedStatement ps =
con.prepareStatement
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)"
+ " values (?,?,?, user, sysdate)");
for (int i = 0; i < count; i++)
{
ps.setInt(1,i);
ps.setString(2,"PS - code" + i);
ps.setString(3,"PS - desc" + i);
ps.executeUpdate();
}
long end = new Date().getTime();
con.commit();
System.out.println
("pstatement " + count + " times in " +
(end - start) + " milli seconds");
}
}
Basically the same code but this uses a PreparedStatement to insert "count" rows. It accomplishes
the same exact task as the doStatement routine - just using a PreparedStatement. Lastly, I set up a
shell script to execute this:
!#/bin/csh -f
sqlplus scott/tiger @perftest
java perftest $1
A CMD file for Windows might look like:
sqlplus scott/tiger @perftest
java perftest %1
Now, I ran this with inputs of 1 (do one statement/prepared statement), 10, 100 and 1,000 and the
results were:
Rows to Insert Statement PrepareStatement
1 0.05 seconds 0.92 seconds
10 0.34 seconds 1.03 seconds
100 2.69 seconds 2.35 seconds
1000 26.68 seconds 15.74 seconds
So, at first glance - it looks like they might have something here. If you were to ignore the
database (which I'm not inclined to do personally). If I just look at this test - I might conclude
that if I'm not going to execute the same statement over and over - about 100 times - I would best
be served by using a Statement. The problem is there is a FLAW in our test! I discovered this flaw
when I rewrote the code a little to go "multi-user". I knew in a multi-user test, using
System.out.println would not be a very "scalable" testing tool. It would be hard to collect and
analyze the results. So, I did what I always do when benchmarking and setup a database table to
hold the timing results. The slightly modified Java code had an extra subroutine "saveTimes" to
save the timing information into the database. That routine you can add to the test program above
is:
static PreparedStatement saveTimesPs;
static void saveTimes( Connection con,
String which,
long elap ) throws Exception
{
if ( saveTimesPs == null )
saveTimesPs = con.prepareStatement
("insert into timings " +
"( which, elap ) values "+
"( ?, ? )" );
saveTimesPs.setString(1,which);
saveTimesPs.setLong(2,elap);
saveTimesPs.executeUpdate();
}
Then, I modified the doStatement and doPreparedStatement routines like this:
static void doStatement (Connection con,
int count) throws Exception
{
long start = new Date().getTime();
Statement st = con.createStatement();
for (int i = 0; i < count; i++)
{
st.executeUpdate
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)" +
" values (" + i +
", 'ST - code" + i + "'" +
", 'St - descr" + i + "'" +
", user, sysdate ) ");
}
st.close();
con.commit();
long end = new Date().getTime();
//System.out.println( "STMT" + " (" + (end-start) + ")" );
saveTimes( con, "STMT", end-start );
}
And I did likewise for the PreparedStatement routine. This would simply save the
times in a database table:
create table timings ( which varchar2(10), elap number );
so we could run a query to get average/min/max timings from multiple users. So, remembering that
the only thing I changed was to comment out the System.out.printlns and add a routine to record the
time - I ran this in single user mode to test. I found:
Rows to Insert Statement PrepareStatement
1 0.05 seconds 0.05 seconds
10 0.30 seconds 0.18 seconds
100 2.69 seconds 1.44 seconds
1000 28.25 seconds 15.25 seconds
That's different - very different. Surprisingly different. All of a sudden - there is not only no
penalty ever for using a PreparedStatement - but it quickly benefits us in single user mode even to
use it. What could be the cause.
The code being timed was no different. Not a single byte of code was changed. Sure, we commented
out a System.out.println and added a call to saveTimes - but that code was never timed before. So,
what did change then? Well, it turns out the saveTimes routine was the culprit here. If you look at
that code - it uses a PreparedStatement. It "warmed up" the PreparedStatement class. It paid a one
time penalty to load that class - java dynamically loads classes as you use them. The simple act of
connecting did that for the Statement class (it is used during the connection to Oracle). Once the
timing of the initial load of the PreparedStatement class was factored out - it turns out that a
PreparedStatement is no more expensive to execute than a Statement is in JDBC. The entire premise
of a Statement being "lighter weight", "more efficient" for small numbers of statements was flawed
- wrong. If you used a single PreparedStatement anywhere in your code - you would have paid this
"load" penalty (which is pretty small when you look at it over all) for ALL PreparedStatements.
That was the interesting part of this example - that the basic test itself was flawed, we were
timing an unrelated "thing". Since most non-trivial Java JDBC programs are going to have to use a
PreparedStatement somewhere - they all pay this "load" penalty. Not only that but this "load
penalty" isn't a penalty at all - but simply the price of admission to building a scalable
application on Oracle. If you don't use Prepared statements - if you insist on using Statements and
"gluing the values in" - opening yourself up to the SQL Injection security flaw and buggy code -
your application will not scale as you add users. There is no "maybe" here, there is no "might not
scale", your application will not scale - period.
Bind Variables and Java
One of the recent questions on the website asktom.Oracle.com recently was this interesting one. It
was about programming in Java with JDBC. This discussion applies equally to Visual Basic
programmers using VB with ODBC as the concept of "Statements" and "PreparedStatements" exists in
ODBC in more or less the same fashion. It questioned the use of Statements versus a
PreparedStatement. When using Statements in JDBC - you must use the "string concatenation
approach". Using PreparedStatements allows you to use bind variables. The question was:
Tom -- Please briefly skim this link (link omitted for obvious reasons) which gives an excerpt for
JDBC performance. It says always use statements (no bind variables allowed) instead of
preparedstatements because they perform better without discussing the impact on the database, only
in terms of a single apps metrics. Is this accurate or is this information just extremely short
sighted with regards to overall db impact?
Well, that was easy - I gave them the proof from above - case closed. PreparedStatements with bind
variables are absolutely without question the only way to do it. Of course, later on, I got a
followup:
For a moment keep aside shared pool, hard parse and soft parse and talk about PreparedStatement and
Statement as they are the only way to execute statements from java. I wrote this benchmark code
that shows a Statement performs better than a PreparedStatement unless you execute the same
statement a whole lot of times. So, I reproduced the findings of the above link and prove that
Statements are better than PreparedStatements.
I had some issues with this one - they missed the point. They start with "for a moment keep aside
shared pool, hard parse and soft parse". Well, if we were to ignore those - we totally miss the
boat on this topic as they are the only things to consider. The facts are:
o Hard Parsing incurs many latches
o Latches are serialization devices
o Serialization is not a scalable thing
o Therefore as you add users, the system that uses Statements instead of PreparedStatements with
bind variables will fail.
I quite simply could not observe their request to put aside the shared pool and hard/soft parse.
They are the relevant topics - they must be considered. That was my initial response - but you
know, this bothered me so much, I had to explore it further. So, starting with their benchmark code
which simply inserted into a database table, I made it a multi-user benchmark to demonstrate the
fact that if you expand this simple, single user benchmark out to a real world example with
multiple users - you will see clearly what the issue is and why you need to avoid statements.
But, an interesting thing happened. I could not reproduce their findings that a Statement in JDBC
without bind variables versus a PreparedStatement using bind variables. When I ran their code - I
could, using my code - I found that a single statement executed using either of a Statement or
PreparedStatement took the same amount of time initially and if we executed the SQL over and over -
the PreparedStatement was always much faster. This conflicted with their observations totally.
So, I set out to find out why. We'll walk through this process here because it does cover two
interesting things:
o If for some reason your test isn't meeting your hypothesis - either your hypothesis is wrong or
your test is flawed
o The seemingly simple, convincing test can be the most misleading thing in the world
We'll walk through this simple benchmark now, starting with their original test case and working up
to the "real thing". It used a single table TESTXXXPERF which was created using the script
perftest.sql as follows:
scott@ORA920> drop table testxxxperf;
Table dropped.
scott@ORA920> create table testxxxperf
2 ( id number,
3 code varchar2(25),
4 descr varchar2(25),
5 insert_user varchar2(30),
6 insert_date date );
Table created.
scott@ORA920> exit
Then, the main java code was supplied. It consisted of three subroutines basically - a main that
connected to the database and then called a routine to insert into that table using statement and
then called a routine to do the same with prepared statements. The code piece by piece is:
import java.sql.*;
import oracle.jdbc.OracleDriver;
import java.util.Date;
public class perftest
{
public static void main (String arr[]) throws Exception
{
Connection con = null;
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
con = DriverManager.getConnection
("jdbc:oracle:thin:@aria-dev:1521:ora920", "scott", "tiger");
con.setAutoCommit(false);
Integer iters = new Integer(arr[0]);
doStatement (con, iters.intValue() );
doPreparedStatement(con, iters.intValue() );
con.commit();
con.close();
}
That is the main routine which simply connects to my Oracle 9iR2 instance as scott/tiger - disables
the autocommit JDBC uses by default and then invokes the subroutine to execute a Statement N times
and then a PreparedStatement N times. I set it up to allow us to pass "N" into the Java routine so
we can run multiple simulations. Next, we'll look at the doStatement routine:
static void doStatement(Connection con, int count)
throws Exception
{
long start = new Date().getTime();
Statement st = con.createStatement();
for (int i = 0; i < count; i++)
{
st.executeUpdate
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)" +
" values (" + i + ", 'ST - code" + i + "'" +
", 'St - descr" + i + "'" + ", user, sysdate ) ");
}
long end = new Date().getTime();
st.close();
con.commit();
System.out.println
("statement " + count + " times in " +
(end - start) + " milli seconds");
}
Very straight forward - it simply creates a statement object and then loops "count" times and
builds a unique - never before seen INSERT statement and executes it. It is somewhat scaled back
from reality in that it is not checking for quotes in strings and fixing them up - but we'll let
that go for now. Also note that it retrieves the time before and after executing the statement and
prints out the results. Next, we look at the prepared statement:
static void doPreparedStatement (Connection con, int count)
throws Exception
{
long start = new Date().getTime();
PreparedStatement ps =
con.prepareStatement
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)"
+ " values (?,?,?, user, sysdate)");
for (int i = 0; i < count; i++)
{
ps.setInt(1,i);
ps.setString(2,"PS - code" + i);
ps.setString(3,"PS - desc" + i);
ps.executeUpdate();
}
long end = new Date().getTime();
con.commit();
System.out.println
("pstatement " + count + " times in " +
(end - start) + " milli seconds");
}
}
Basically the same code but this uses a PreparedStatement to insert "count" rows. It accomplishes
the same exact task as the doStatement routine - just using a PreparedStatement. Lastly, I set up a
shell script to execute this:
!#/bin/csh -f
sqlplus scott/tiger @perftest
java perftest $1
A CMD file for Windows might look like:
sqlplus scott/tiger @perftest
java perftest %1
Now, I ran this with inputs of 1 (do one statement/prepared statement), 10, 100 and 1,000 and the
results were:
Rows to Insert Statement PrepareStatement
1 0.05 seconds 0.92 seconds
10 0.34 seconds 1.03 seconds
100 2.69 seconds 2.35 seconds
1000 26.68 seconds 15.74 seconds
So, at first glance - it looks like they might have something here. If you were to ignore the
database (which I'm not inclined to do personally). If I just look at this test - I might conclude
that if I'm not going to execute the same statement over and over - about 100 times - I would best
be served by using a Statement. The problem is there is a FLAW in our test! I discovered this flaw
when I rewrote the code a little to go "multi-user". I knew in a multi-user test, using
System.out.println would not be a very "scalable" testing tool. It would be hard to collect and
analyze the results. So, I did what I always do when benchmarking and setup a database table to
hold the timing results. The slightly modified Java code had an extra subroutine "saveTimes" to
save the timing information into the database. That routine you can add to the test program above
is:
static PreparedStatement saveTimesPs;
static void saveTimes( Connection con,
String which,
long elap ) throws Exception
{
if ( saveTimesPs == null )
saveTimesPs = con.prepareStatement
("insert into timings " +
"( which, elap ) values "+
"( ?, ? )" );
saveTimesPs.setString(1,which);
saveTimesPs.setLong(2,elap);
saveTimesPs.executeUpdate();
}
Then, I modified the doStatement and doPreparedStatement routines like this:
static void doStatement (Connection con,
int count) throws Exception
{
long start = new Date().getTime();
Statement st = con.createStatement();
for (int i = 0; i < count; i++)
{
st.executeUpdate
("insert into testxxxperf " +
"(id, code, descr, insert_user, insert_date)" +
" values (" + i +
", 'ST - code" + i + "'" +
", 'St - descr" + i + "'" +
", user, sysdate ) ");
}
st.close();
con.commit();
long end = new Date().getTime();
//System.out.println( "STMT" + " (" + (end-start) + ")" );
saveTimes( con, "STMT", end-start );
}
And I did likewise for the PreparedStatement routine. This would simply save the
times in a database table:
create table timings ( which varchar2(10), elap number );
so we could run a query to get average/min/max timings from multiple users. So, remembering that
the only thing I changed was to comment out the System.out.printlns and add a routine to record the
time - I ran this in single user mode to test. I found:
Rows to Insert Statement PrepareStatement
1 0.05 seconds 0.05 seconds
10 0.30 seconds 0.18 seconds
100 2.69 seconds 1.44 seconds
1000 28.25 seconds 15.25 seconds
That's different - very different. Surprisingly different. All of a sudden - there is not only no
penalty ever for using a PreparedStatement - but it quickly benefits us in single user mode even to
use it. What could be the cause.
The code being timed was no different. Not a single byte of code was changed. Sure, we commented
out a System.out.println and added a call to saveTimes - but that code was never timed before. So,
what did change then? Well, it turns out the saveTimes routine was the culprit here. If you look at
that code - it uses a PreparedStatement. It "warmed up" the PreparedStatement class. It paid a one
time penalty to load that class - java dynamically loads classes as you use them. The simple act of
connecting did that for the Statement class (it is used during the connection to Oracle). Once the
timing of the initial load of the PreparedStatement class was factored out - it turns out that a
PreparedStatement is no more expensive to execute than a Statement is in JDBC. The entire premise
of a Statement being "lighter weight", "more efficient" for small numbers of statements was flawed
- wrong. If you used a single PreparedStatement anywhere in your code - you would have paid this
"load" penalty (which is pretty small when you look at it over all) for ALL PreparedStatements.
That was the interesting part of this example - that the basic test itself was flawed, we were
timing an unrelated "thing". Since most non-trivial Java JDBC programs are going to have to use a
PreparedStatement somewhere - they all pay this "load" penalty. Not only that but this "load
penalty" isn't a penalty at all - but simply the price of admission to building a scalable
application on Oracle. If you don't use Prepared statements - if you insist on using Statements and
"gluing the values in" - opening yourself up to the SQL Injection security flaw and buggy code -
your application will not scale as you add users. There is no "maybe" here, there is no "might not
scale", your application will not scale - period.
相关推荐
在Java编程中,读取XML文件并从中获取Oracle数据库连接是一项常见的任务,特别是在需要动态配置数据库连接信息的应用中。XML文件通常被用作配置文件,因为它具有良好的结构和可读性,适合存储像数据库连接这样的配置...
《DNS and Bind》第五版与《Pro DNS and Bind 10》是DNS系统管理员和网络专业人士的必备参考资料,这两本书深入浅出地介绍了DNS(Domain Name System)与BIND(Berkeley Internet Name Domain)的原理、配置和管理。...
Pro DNS and BIND guides you through the challenging array of features surrounding DNS, with a special focus on BIND, the worlds most popular DNS implementation. This book unravels the mysteries of DNS...
标题 "javax.xml.bind jdk1.5缺包" 指出的问题是在使用Java 1.5版本时,遇到了与`javax.xml.bind`相关的类找不到的异常。具体来说,异常信息 "java.lang.ClassNotFoundException: ...
《Pro DNS and BIND 10英文版》是一本深入探讨DNS(域名系统)及其流行实现 BIND 的权威指南,特别是 BIND 10 第一稳定版本的相关内容。BIND 是伯克利互联网名字域(Berkeley Internet Name Domain)的缩写,是最...
根据提供的信息,“dns-and-bind.pdf”是一本详细讲解域名系统(DNS)与Berkeley Internet Name Domain(BIND)软件的专业书籍。这本书由Cricket Liu和Paul Albitz共同编写,ISBN号为1−56592−512−2,共502页,...
《DNS and BIND 第五版》是网络管理员和IT专业人员不可或缺的参考资料,它详细阐述了域名系统(DNS)以及BIND(Berkeley Internet Name Domain)服务器的运作原理与配置方法。DNS是互联网上的一种核心服务,它负责将...
【Java中的`java.net.BindException: Address already in use: JVM_Bind`异常】 在Java编程中,当你尝试启动一个服务器端应用,如Tomcat,或者任何需要监听特定端口的服务时,可能会遇到`java.net.BindException: ...
包含三本书 Dns and Bind Cookbook.chm R.G.F.Aitchison - Pro DNS and BIND. 2005.pdf DNS and BIND 5th Edition.chm
两个 SQL 语句中必须使用相同的名字的绑定变量(bind variables)。 三、选择最有效率的表名顺序 ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名,因此 FROM 子句中写在最后的表(基础表 driving ...
BIND9是互联网域名系统(DNS)服务器软件,广泛用于管理和解析域名。中文手册的提供是为了帮助中文用户更好地理解和操作这个强大的系统。以下是BIND9的一些关键知识点: 1. **DNS概念**:BIND9作为DNS服务器,负责...
5. **性能优化**:通过合理使用绑定变量(bind variables)、预编译的SQL语句(prepare statement)和批量处理(array fetch size)等技术,可以提高DBD::Oracle的执行效率。 6. **安全考虑**:在编写数据库操作...
### 关于《Pro DNS and BIND》一书的知识点解析 #### 书籍基本信息 - **书名**:《Pro DNS and BIND》 - **作者**:Ron Aitchison - **出版社**:Apress - **出版年份**:2005年 - **ISBN**:1-59059-494-0 - **...
BIND9 安装配置在 FreeBSD 上 BIND9 是一个功能强大且广泛使用的 DNS 服务器软件,本文将介绍如何在 FreeBSD 上安装和配置 BIND9,以便提供 DNS 服务。 安装 BIND9 在 FreeBSD 上安装 BIND9非常简单,只需要使用...
BIND,全称Berkeley Internet Name Domain,是互联网上最广泛使用的域名系统(DNS)服务器软件。这个"bind-9.8.4.tar.gz"安装包是用于在Linux操作系统上搭建DNS服务器的关键组件。以下是关于BIND及其安装过程的详细...
BIND9管理员参考手册-9.18.0 本文档是 BIND 9 管理员参考手册的中文翻译版本,旨在帮助管理员和开发者更好地理解和使用 BIND 9。在本文档中,我们将详细介绍 BIND 9 的基本概念、安装和配置、管理和维护、故障排除...