进一步提高JDBC应用程序的性能 (四)
bootcool@263.net
四:使用预编译语句和批量更新
首先我们得大致的了解数据库是怎么处理各种数据库操作语句的。当数据库接收到一个语句时,数据库引擎首先解析该语句,然后分析是否有语法,语义错误。如果没有错误,数据库将计算出如何高效的执行该语句。一旦得出执行策略,就由数据库引擎执行该语句,最后把执行结果反馈给用户。虽然数据库厂商对各自的数据库做了最大的优化,但是可以想象这的确是一个开销很大的工作。
于是,我们考虑如何使我们的数据库操作变得更高效呢?如果一条语句执行一次后,数据库就记录下该语句的执行策略,那么以后执行相同语句时,就可以省去上面的种种麻烦了。
Java里提供了这样的接口――PreparedStatement.。通过预编译PreparedStatement 对象, 我们能够很容易的提高语句执行效率。同时,需要指出的是Java里还有另一个实现数据库操作的接口――Statement,但是当语句格式固定时我们更倾向于使用PreparedStatement,只有当语句格式无法预见时,我们才考虑采用Statement。
以下是执行1000次语句结构相同的Insert,Update和Select语句的测试结果:
接口类型 | Insert语句 | Update语句 | Select语句 | |||
第一次测试耗时 | 第二次测试耗时 | 第一次测试耗时 | 第二次测试耗时 | 第一次测试耗时 | 第二次测试耗时 | |
Statement | 2360 ms | 2190 ms | 3790 ms | 3460 ms | 3570 ms | 2530 ms |
PreparedStatement | 1270 ms | 1040 ms | 2600 ms | 2410 ms | 440 ms | 380 ms |
(表8)
分析: PreparedStatement的效率明显比Statement要高出很多。另外,对于查询语句我们还得深入地看看JDBC是如何实现的。JDBC执行一次查询后,将返回一个ResultSet(结果集)。为了建立这个结果集,JDBC将对数据库访问两次。第一次要求数据库对结果集中的各列进行说明,第二次告诉数据库,当程序需要获取数据时应如何安置这些数据。由此我们能够算出执行一次或多次查询,JDBC需要访问数据库的次数。
访问数据库次数 = 结果集中的列数 * 语句执行的次数 * 2
如果同样执行100次相同查询,结果集中的列数也相同时,假设为20列:
使用Statement: 访问数据库次数 = 20 * 100 * 2 = 4000
使用Prepared Statement: 访问数据库次数 = 20 * 1* 2 = 400
我们还要注意一点,如果使用PreparedStatement接口的方法不当,则不能达到提高执行效率的目的。我们用一个简单的测试程序说明:
import java.sql.*; public class JDBCTEST2 { private static Connection con = null; private static String dbUrl = null; public JDBCTEST2(){ } public static void main(String args[]){ try{ dbUrl = "jdbc:odbc:test"; Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); preparedStatementInsertTest_1(); System.out.println("==================="); preparedStatementInsertTest_2(); } catch(Exception ex){ ex.printStackTrace(); } } //方法1以不恰当的方式使用了PreparedStatement接口 public static void preparedStatementInsertTest_1(){ try{ con = DriverManager.getConnection(dbUrl); PreparedStatement pst = null; long start = System.currentTimeMillis(); //执行1000次语句结构相同的查询 for(int i=0;i<1000;i++){ pst = con.prepareStatement("select * from s where s1 = " + i); pst.executeQuery(); pst.close(); } System.out.println("Methord_1 Execute Ellapse:" +(System.currentTimeMillis()-start) +"ms"); con.close(); } catch(Exception ex){ ex.printStackTrace(); } }
//方法2以正确的方式使用了PreparedStatement接口 public static void preparedStatementInsertTest_2(){ try{ con = DriverManager.getConnection(dbUrl); long start = System.currentTimeMillis(); PreparedStatement pst = null; pst = con.prepareStatement("select * from s where s1 = ?"); //执行1000次语句结构相同的查询 for(int i=0;i<1000;i++){ pst.setInt(1,i); pst.executeQuery(); } System.out.println("Methord_2 Execute Ellapse:" +(System.currentTimeMillis()-start) +"ms"); pst.close(); con.close(); } catch(Exception ex){ ex.printStackTrace(); } } }
以下是相关的测试结果:
方式 | Select语句 | |||
执行100次语句结构相同的查询耗时 | 执行1000次语句结构相同的查询耗时 | |||
第一次测试 | 第二次测试 | 第一次测试 | 第二次测试 | |
方式1 | 1100 ms | 330 ms | 3510 ms | 3020 ms |
方式2 | 110 ms | 50 ms | 440 ms | 380 ms |
(表9)
分析:测试结果说明,如果不正确的使用了PreparedStatement接口,那么其执行效率和使用Statement没有什么差别,而PreparedStatement接口的优势也不会得到充分发挥。
最后我们还得补充一点,当我们需要成批插入或者更新记录时。我们考虑采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。如果我们再配合使用PreparedStatement接口,将进一步提高程序的性能。我们同样给出一个小程序予以说明。
import java.sql.*;
public class JDBCTEST3 {
public static void main(String[] args) { try {String[] values = {"BeiJing","KunMing"}; int[] results;
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection("jdbc:odbc:test"); Statement st = con.createStatement (); for (int i = 0; i < values.length; i++){ //把一个SQL命令加入命令列表 st.addBatch ("INSERT INTO CITY VALUES('" + values[i] + "')"); } //执行批量更新 st.executeBatch ();
PreparedStatement pst = con.prepareStatement("INSERT INTO CITY" +"VALUES (?)"); for (int i = 0; i < values.length; i++) { pst.setString(1, values[i]); //把一个SQL命令加入命令列表 pst.addBatch(); } //执行批量更新 pst.executeBatch(); st.close(); pst.close(); con.close(); } catch(Exception ex){ ex.printStackTrace(); } } }