JDBC效能優化及連線池
君子的力量永遠是行動的力量,而不是語言的力量。
批量增刪改
對10萬條資料進行增加,採取3種方式來進行增加對比效果。
由於CPU資源有限, 所以可能耗時比較長,但是好的是都在一個CPU下進行的採用控制變數法可以很好的對比出來。
1. 採用PrepraredStatement 進行增加(執行一條增加一條)
public class TestJdbc2 { public static void main(String[] args) { try(Connection conn = JdbcUtils.getConn()) { String sql="insert into user(id,name) values (null,?)"; try(PreparedStatement psmt = conn.prepareStatement(sql)){ long start = System.currentTimeMillis(); for (int i = 1; i < 10000; i++) { psmt.setString(1,"zhang"+i); psmt.executeUpdate(); } long end = System.currentTimeMillis(); System.out.println("總共花費了"+(end - start)+"毫秒"); } } catch (SQLException e) { e.printStackTrace(); }
結果
耗時: 34024毫秒
2.採用Batch,將10萬條記錄放入其中,最後一起執行
public class TestJdbc2 { public static void main(String[] args) { try(Connection conn = JdbcUtils.getConn()) { String sql="insert into user(id,name) values (null,?)"; try(PreparedStatement psmt = conn.prepareStatement(sql)){ long start = System.currentTimeMillis(); for (int i = 1; i < 10000; i++) { psmt.setString(1,"zhang"+i); psmt.addBatch(); } psmt.executeBatch(); long end = System.currentTimeMillis(); System.out.println("總共花費了"+(end - start)+"毫秒"); } } catch (SQLException e) { e.printStackTrace(); } } }
結果
耗時:33040
3.將10萬條記錄加入Batch,每1萬進行執行一次
ublic class TestJdbc2 { public static void main(String[] args) { try(Connection conn = JdbcUtils.getConn()) { String sql="insert into user(id,name) values (null,?)"; try(PreparedStatement psmt = conn.prepareStatement(sql)){ long start = System.currentTimeMillis(); for (int i = 1; i < 10000; i++) { psmt.setString(1,"zhang"+i); psmt.addBatch(); if (i %10000 == 0){ psmt.executeBatch(); } } long end = System.currentTimeMillis(); System.out.println("總共花費了"+(end - start)+"毫秒"); } } catch (SQLException e) { e.printStackTrace(); } }
結果
耗時:31528
由此可鑑,第三種沒加入1萬就執行一次的效率最高。
查詢效能優化
1.設定最優的預取值
defaultRowPrefetch:預取條數預設值
defaultBatchValue:觸發查詢操作的批量請求值
這兩個引數的預設值都是10,我們可以通過增加這兩個引數值來減少資料庫請求以提高查詢效率,當然具體值大小要視具體情況而定。
2.通過連線池獲取連線
建立連線的代價很大,通過連線池獲取連線可省去建立連線時間。
3.設定檢索時的批量值
Statement.getFetchSize(); 獲取一次檢索的批量值
Statement.setFetchSize(30); 設定批量
4.設定ResultSet的批量值
ResultSet.getFetchSize(); 獲取預設批量值
ResultSet.setFetchSize(50); 設定批量值
處理大資料時可顯著提高處理速度
5.優化查詢SQL
比如避免使用select * from table where condition…,因為這麼做會把所有的資料專案查詢出來,比如我們只需要Salary的話,我們就寫成select salary from employee where name=RR,避免不必要資料的檢索。
6.迭代分批次獲取資料替代一次大批量獲取資料
某些情況下,應用程式可能會通過JDBC一次請求大量資料,而應用程式可能會一次把所有資料返回給客戶端,這樣會用掉很多時間,可以採取如下方式解決:
在Server端快取資料,分批次發給Client端,比如Server端查詢出1000條資料,可以分10批次每次傳送100條給Client端不在Server端快取資料,而通過儲存過程迭代的返回小批量資料。
其實對於查詢最有效的還是建立索引:
建立資料庫索引
索引是對現有的資料進行排序,在排序的結果中搜索,效率會很高
索引的資料結構是:B+樹
create index 索引名 on 表(列);
// 向 big 的name 建立一個索引
create index idx_name on big(name);
mysql的主鍵會自動建立索引,用主鍵作為查詢條件,搜尋速度最快
而建立索引後,對於查詢的sql語句還有影響,
示例;
id name sex
1 張三 男
id 為主鍵,在name 建立索引,那麼name建立索引後上就存放有id,name 並沒有sex。
假如現在查詢id,name 不查詢sex的值,那麼查詢速度最快,只需要查詢name索引即可。
如果還要查詢sex的值,那麼此時,就要二次查詢,先通過name索引查到主鍵id,然後通過主鍵id再查詢sex。所以就進行了二次查詢,相對速度就慢了。
示例
未建立索引前查詢
mysql> select * from user where name like 'zhang222%' order by id desc;
+------+----------+
| id | name |
+------+----------+
| 2190 | zhang222 |
| 1221 | zhang222 |
| 222 | zhang222 |
+------+----------+
3 rows in set (0.01 sec)
用時0.01
建立索引
create index idx_name on user(name);
執行查詢
mysql> select * from user where name like 'zhang222%' order by id desc;
+------+----------+
| id | name |
+------+----------+
| 2190 | zhang222 |
| 1221 | zhang222 |
| 222 | zhang222 |
+------+----------+
3 rows in set (0.00 sec)
用時0.00
由此可鑑,建立索引的優越性。
刪除索引
alter table 表名 drop key 索引名
alter table user drop key idx_name;
SQL語句的執行順序
-
sql 傳送給資料庫伺服器
連線層 -
管理連線,驗證連線是否有效(連線池)
服務層 -
查詢快取處理,快取中已有的資料直接返回, 從mysql 8.0開始,這個功能沒有了
select * from student 10 條學生記錄放入了查詢快取insert ... update delete
-
sql 需要詞法分析、語法分析,生成語法解析樹 – 由分析器完成
select * from student where id = 1;
select * from student where id = 2;
select * from student where id = 3;select * from student where id = ?
? 1
? 2
mysql 中預設沒有開啟這個功能,需要開啟開關:
useServerPrepStmts=true
cachePrepStmts=true
prepStmtCacheSize=25 -
進行sql的優化處理,選擇索引 – 由優化器
-
呼叫儲存引擎 – 執行器
儲存引擎層 -
去索引搜尋,讀取磁碟資料
mysql 預設的儲存引擎是InnoDB( 支援事務,支援外來鍵,聚簇索引按(主鍵順序排列,在葉子節點儲存所有記錄) )
MyISAM( 不支援事務,不支援外來鍵, 不支援災難恢復 )
Memory( 記憶體 , hash索引, 只能支援等值比較)
連線池
apache dbcp 最老牌
c3p0 連線池
alibaba druid (德魯伊) sql監控
javax.sql.DataSource 連線池介面
Conneciton conn = datasource.getConnection(); 從池中返回連線物件
conn.close(); // 一般都覆蓋了關閉方法,將連線歸還給連線池,而不是真正關閉
- 用DriverManager 獲取連線,稱為直連
- 用DataSource 獲取連線,稱為池連
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
// dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 可選步驟,註冊驅動
dataSource.setInitialSize(5); // 初始連線數
dataSource.setMaxActive(10); // 最大連線數
dataSource.setMinIdle(5); // 最小連線數
dataSource.setValidationQuery("select 1"); // 一條簡單的sql語句,用來保活
dataSource.setTestWhileIdle(true); // 當空閒時時不時檢查一下連線的有效性, 利用ValidationQuery中的sql語句
dataSource.setTimeBetweenEvictionRunsMillis(60*1000); // 預設一分鐘
try(Connection conn = dataSource.getConnection()){
// conn.close();// 歸還連線池了
}
DruidDataSource配置相容DBCP幫助手冊連結地址:https://github.com/alibaba/druid/wiki/DruidDataSource配置屬性列表