1. 程式人生 > >JDBC效能優化及連線池

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語句的執行順序

  1. sql 傳送給資料庫伺服器
    連線層

  2. 管理連線,驗證連線是否有效(連線池)
    服務層

  3. 查詢快取處理,快取中已有的資料直接返回, 從mysql 8.0開始,這個功能沒有了
    select * from student 10 條學生記錄放入了查詢快取

     insert ... 
     update
     delete
    
  4. 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

  5. 進行sql的優化處理,選擇索引 – 由優化器

  6. 呼叫儲存引擎 – 執行器
    儲存引擎層

  7. 去索引搜尋,讀取磁碟資料
    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配置屬性列表