连接池优化之启用PoolPreparedStatements

来源:http://blog.itpub.net/29254281/viewspace-1064007/

DBCP连接池可以缓存PreparedStatement,本质上就是缓存游标。
    一个SQL语句,无论是Insert,Update,Delete还是Select都是游标操作,只不过Select游标指向查询结果,而其余的指向修改的目标。

    除了连接可以缓存,游标也是可以缓存的,主要是避免游标的反复创建。虽然Oracle对完全相同的SQL可以共享执行计划,但是也需要去共享池查询这个SQL的信息(该SQL的Hash值是否在共享池内)。缓存游标,则进一步优化,避免了反复查询共享池的操作(个人臆测).
    首先,做一个实验,证明游标可以反复利用。
    

  1. --创建实验表
  2. create table t as select rownum r from dual connect by level<10;
  3. set serveroutput on
  4. declare
  5.     cursor cur is select * from t;
  6.     v_record t%rowtype;    
  7. begin
  8.     open cur;
  9.     fetch cur into v_record;
  10.     dbms_output.put_line(v_record.r);
  11.     fetch cur into v_record;
  12.     dbms_output.put_line(v_record.r);
  13.     close cur;
  14.     
  15.     open cur;
  16.     fetch cur into v_record;
  17.     dbms_output.put_line(v_record.r);
  18.     fetch cur into v_record;
  19.     dbms_output.put_line(v_record.r);
  20.     close cur;
  21.     
  22. end;
  23. /
   
    实验结果:
            1
            2
            1
            2

可以看到游标在关闭之后,可以重新打开。并且重新打开的游标,与前次打开的游标,在数据上没有任何关系。第一次读到2,重新打开之后,会从1开始,而不是从3开始。

这个代码如果在JAVA程序中,就是这个样子的。

  1.         Class.forName("oracle.jdbc.OracleDriver");
  2.         Connection conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
  3.         PreparedStatement cmd = conn.prepareStatement("select * from t");
  4.         //第一次调用
  5.         ResultSet rs = cmd.executeQuery();
  6.         rs.next();
  7.         System.out.println(rs.getString(1));
  8.         rs.next();
  9.         System.out.println(rs.getString(1));
  10.         //第二次调用
  11.         rs = cmd.executeQuery();
  12.         rs.next();
  13.         System.out.println(rs.getString(1));
  14.         rs.next();
  15.         System.out.println(rs.getString(1));
  16.         cmd.close();
  17.         conn.close();

    值得注意的是,PreparedStatement就表示Oracle的游标,但是一旦PreparedStatement关闭,就无法重新打开。所以复用PreparedStatement只需要在关闭之前重新调用executeQuery方法即可。
   
    如果连接池启动PoolPreparedStatements,则可能在每一个Connection的代理对象中,包括下面的结构
    Map<STRING, List> poolPreparedStatements
    其中Key是SQL语句或者SQL语句的Hash值,代理的Connection会根据SQL返回一个可用的prepareStatement;如果没有,则会创建新的prepareStatement对象。而这个返回的prepareStatement对象,也同样是代理对象。
    因为在调用连接池返回的prepareStatement的close方法时,不会真正的close这个对象,因为这样就无法实现复用的效果。可能只是修改了这个对象的标志位,标明其可用。

    下面是DBCP连接池开启游标缓存的
代码。
    可以想见 ds.getConnection()返回的Connection和PreparedStatement应该都是代理对象。
  1.     private static void testDataSource() throws SQLException {
  2.         BasicDataSource ds = new BasicDataSource();
  3.         ds.setUrl("jdbc:oracle:thin:127.0.0.1:1521:orcl");
  4.         ds.setUsername("edmond");
  5.         ds.setPassword("edmond");
  6.         ds.setPoolPreparedStatements(true);
  7.         ds.setMaxOpenPreparedStatements(300);
  8.         Connection conn = ds.getConnection();
  9.         PreparedStatement cmd = conn.prepareStatement("select * from t");
  10.         ResultSet rs = cmd.executeQuery();
  11.         rs.next();
  12.         System.out.println(rs.getString(1));
  13.         rs.next();
  14.         System.out.println(rs.getString(1));
  15.         cmd.close();
  16.         conn.close();
  17.     }
    另外,Oracle游标对应的是PreparedStatement,而不是ResultSet。
    并且MaxOpenPreparedStatements的设置应该小于Oracle的Open_Cursor的数值。
    
  1. public static void main(String[] args) throws ClassNotFoundException, SQLException {
  2.         List<PreparedStatement> list = new ArrayList<PreparedStatement>();
  3.         Class.forName("oracle.jdbc.OracleDriver");
  4.         Connection conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
  5.         for (int i = 0; i < 305; i++) {
  6.             PreparedStatement cmd = conn.prepareStatement("select * from t");
  7.             
  8.             ResultSet rs = cmd.executeQuery();
  9.             rs.next();
  10.             rs.close();
  11.             rs = null;
  12.             list.add(cmd);
  13.         }
  14.         conn.close();
  15.     }
    结果出现异常:
Exception in thread "main" java.sql.SQLException: ORA-00604: 递归 SQL 级别 1 出现错误
ORA-01000: 超出打开游标的最大数
ORA-00604: 递归 SQL 级别 1 出现错误
ORA-01000: 超出打开游标的最大数
ORA-01000: 超出打开游标的最大数

    可以看到,如果PreparedStatement没有关闭,则Oracle那端的游标就没有释放。
    最终这个连接的游标超过Oracle的open_cursor数值(默认300),就会报错。
    所以启用了PoolPreparedStatements,一定注意设置MaxOpenPreparedStatements小于Oracle Open_Cursor的数值。    

   

猜你喜欢

转载自dongxylove.iteye.com/blog/2248518
今日推荐