連線池優化之啟用PoolPreparedStatements
DBCP連線池可以快取PreparedStatement,本質上就是快取遊標。
一個SQL語句,無論是Insert,Update,Delete還是Select都是遊標操作,只不過Select遊標指向查詢結果,而其餘的指向修改的目標。
除了連線可以快取,遊標也是可以快取的,主要是避免遊標的反覆建立。雖然Oracle對完全相同的SQL可以共享執行計劃,但是也需要去共享池查詢這個SQL的資訊(該SQL的Hash值是否在共享池內)。快取遊標,則進一步優化,避免了反覆查詢共享池的操作(個人臆測).
首先,做一個實驗,證明遊標可以反覆利用。
實驗結果:
1
2
1
2
可以看到遊標在關閉之後,可以重新開啟。並且重新開啟的遊標,與前次開啟的遊標,在資料上沒有任何關係。第一次讀到2,重新開啟之後,會從1開始,而不是從3開始。
這個程式碼如果在JAVA程式中,就是這個樣子的。
值得注意的是,PreparedStatement就表示Oracle的遊標,但是一旦PreparedStatement關閉,就無法重新開啟。所以複用PreparedStatement只需要在關閉之前重新呼叫executeQuery方法即可。
如果連線池啟動PoolPreparedStatements,則可能在每一個Connection的代理物件中,包括下面的結構
Map> poolPreparedStatements
其中Key是SQL語句或者SQL語句的Hash值,代理的Connection會根據SQL返回一個可用的prepareStatement;如果沒有,則會建立新的prepareStatement物件。而這個返回的prepareStatement物件,也同樣是代理物件。
因為在呼叫連線池返回的prepareStatement的close方法時,不會真正的close這個物件,因為這樣就無法實現複用的效果。可能只是修改了這個物件的標誌位,標明其可用。
一個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
這個程式碼如果在JAVA程式中,就是這個樣子的。
-
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();
如果連線池啟動PoolPreparedStatements,則可能在每一個Connection的代理物件中,包括下面的結構
Map
其中Key是SQL語句或者SQL語句的Hash值,代理的Connection會根據SQL返回一個可用的prepareStatement;如果沒有,則會建立新的prepareStatement物件。而這個返回的prepareStatement物件,也同樣是代理物件。
因為在呼叫連線池返回的prepareStatement的close方法時,不會真正的close這個物件,因為這樣就無法實現複用的效果。可能只是修改了這個物件的標誌位,標明其可用。
下面是DBCP連線池開啟遊標快取的
程式碼。
可以想見 ds.getConnection()返回的Connection和PreparedStatement應該都是代理物件。
另外,Oracle遊標對應的是PreparedStatement,而不是ResultSet。
並且MaxOpenPreparedStatements的設定應該小於Oracle的Open_Cursor的數值。
可以看到,如果PreparedStatement沒有關閉,則Oracle那端的遊標就沒有釋放。
最終這個連線的遊標超過Oracle的open_cursor數值(預設300),就會報錯。
所以啟用了PoolPreparedStatements,一定注意設定MaxOpenPreparedStatements小於Oracle Open_Cursor的數值。
可以想見 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();
- }
並且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: 超出開啟遊標的最大數
Exception in thread "main" java.sql.SQLException: ORA-00604: 遞迴 SQL 級別 1 出現錯誤
ORA-01000: 超出開啟遊標的最大數
ORA-00604: 遞迴 SQL 級別 1 出現錯誤
ORA-01000: 超出開啟遊標的最大數
ORA-01000: 超出開啟遊標的最大數
最終這個連線的遊標超過Oracle的open_cursor數值(預設300),就會報錯。
所以啟用了PoolPreparedStatements,一定注意設定MaxOpenPreparedStatements小於Oracle Open_Cursor的數值。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29254281/viewspace-1064007/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 深度學習筆記1:池化 全連線 啟用函式 softmax深度學習筆記函式
- Jdbc引入連線池,JdbcTemplate處理結果集的優化JDBC優化
- 【ITOO】--SQL資料庫優化:切割、資料庫連線池SQL資料庫優化
- PG 資料庫連線池寫法優化一例資料庫優化
- Android應用優化之冷啟動優化Android優化
- 【JDBC】使用OracleDataSource建立連線池用於連線OracleJDBCOracle
- Spring系列之HikariCP連線池Spring
- 連線池
- PostgreSQL優化案例之 反連線與外連線等價改寫SQL優化
- 用sqlalchemy構建Django連線池SQLDjango
- Java Hibernate 之連線池詳解Java
- Go連線池Go
- HTTP連線池HTTP
- django連線池Django
- Android效能優化之App應用啟動分析與優化Android優化APP
- rpc框架之thrift連線池實現RPC框架
- MySQL表連線及其優化MySql優化
- 資料庫連線池優化配置(druid,dbcp,c3p0)資料庫優化UI
- Http持久連線與HttpClient連線池HTTPclient
- 連線池和連線數詳解
- 2-2. 線性池技術優化優化
- 【資料庫】查詢優化之子連線優化資料庫優化
- ElasticSearch連線池建立Elasticsearch
- 自定義連線池
- golang tcp連線池GolangTCP
- Oracle 連線池配置Oracle
- oracle occi 連線池Oracle
- Resin 配置連線池
- [尋]連線池例子
- proxool連線池 配置
- 連線池 druidUI
- Oracle優化案例-用left join代替反連線 not in not exists(十)Oracle優化
- java連線池解決連線中斷Java
- proxool連線池如何使用SSL方式連線?
- OkHttp3原始碼分析[複用連線池]HTTP原始碼
- 遊戲開發效能優化之物件池遊戲開發優化物件
- Android效能優化之加快應用啟動速度Android優化
- Golang SQL連線池梳理GolangSQL