連接池優化之啓用PoolPreparedStatements

 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的數值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章