连接池优化之启用PoolPreparedStatements

论坛 期权论坛 编程之家     
选择匿名的用户   2021-6-2 20:34   1117   0

DBCP连接池可以缓存PreparedStatement,本质上就是缓存游标。
一个SQL语句,无论是Insert,Update,Delete还是Select都是游标操作,只不过Select游标指向查询结果,而其余的指向修改的目标。
除了连接可以缓存,游标也是可以缓存的,主要是避免游标的反复创建。虽然Oracle对完全相同的SQL可以共享执行计划,但是也需要去共享池查询这个SQL的信息(该SQL的Hash值是否在共享池内)。缓存游标,则进一步优化,避免了反复查询共享池的操作(个人臆测).
首先,做一个实验,证明游标可以反复利用。

--创建实验表
create table t as select rownum r from dual connect by level<10;

set serveroutput on

declare 
    cursor cur is select * from t;
    v_record t%rowtype;    
begin
    open cur;
    fetch cur into v_record;
    dbms_output.put_line(v_record.r);
    fetch cur into v_record;
    dbms_output.put_line(v_record.r);
    close cur;
    
    open cur;
    fetch cur into v_record;
    dbms_output.put_line(v_record.r);
    fetch cur into v_record;
    dbms_output.put_line(v_record.r);
    close cur;
    
end;
/


实验结果:
1
2
1
2

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

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

  1.  Class.forName("oracle.jdbc.OracleDriver");
            Connection conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
            PreparedStatement cmd = conn.prepareStatement("select * from t");
            //第一次调用
            ResultSet rs = cmd.executeQuery();
            rs.next();
            System.out.println(rs.getString(1));
            rs.next();
            System.out.println(rs.getString(1));
    
            //第二次调用
            rs = cmd.executeQuery();
            rs.next();
            System.out.println(rs.getString(1));
            rs.next();
            System.out.println(rs.getString(1));
            cmd.close();
            conn.close();

值得注意的是,PreparedStatement就表示Oracle的游标,但是一旦PreparedStatement关闭,就无法重新打开。所以复用PreparedStatement只需要在关闭之前重新调用executeQuery方法即可。

如果连接池启动PoolPreparedStatements,则可能在每一个Connection的代理对象中,包括下面的结构
Map> poolPreparedStatements
其中Key是SQL语句或者SQL语句的Hash值,代理的Connection会根据SQL返回一个可用的prepareStatement;如果没有,则会创建新的prepareStatement对象。而这个返回的prepareStatement对象,也同样是代理对象。
因为在调用连接池返回的prepareStatement的close方法时,不会真正的close这个对象,因为这样就无法实现复用的效果。可能只是修改了这个对象的标志位,标明其可用。

下面是DBCP连接池开启游标缓存的 代码。
可以想见 ds.getConnection()返回的Connection和PreparedStatement应该都是代理对象。

private static void testDataSource() throws SQLException {
        BasicDataSource ds = new BasicDataSource();
        ds.setUrl("jdbc:oracle:thin:127.0.0.1:1521:orcl");
        ds.setUsername("edmond");
        ds.setPassword("edmond");
        ds.setPoolPreparedStatements(true);
        ds.setMaxOpenPreparedStatements(300);

        Connection conn = ds.getConnection();
        PreparedStatement cmd = conn.prepareStatement("select * from t");
        ResultSet rs = cmd.executeQuery();
        rs.next();
        System.out.println(rs.getString(1));
        rs.next();
        System.out.println(rs.getString(1));
        cmd.close();
        conn.close();
    }

另外,Oracle游标对应的是PreparedStatement,而不是ResultSet。
并且MaxOpenPreparedStatements的设置应该小于Oracle的Open_Cursor的数值。

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        List<PreparedStatement> list = new ArrayList<PreparedStatement>();

        Class.forName("oracle.jdbc.OracleDriver");
        Connection conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
        for (int i = 0; i < 305; i++) {
            PreparedStatement cmd = conn.prepareStatement("select * from t");
            
            ResultSet rs = cmd.executeQuery();
            rs.next();
            rs.close();
            rs = null;
            list.add(cmd);
        }
        conn.close();
    }

结果出现异常:
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的数值。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP