-合理的設置fetchsize


http://www.dbafree.net/?p=597

在前面的幾節中,我們經常會提到一個詞“Fetchsize”,並且在《JBOSS連接池調優2-合理的設置PreparedStatementCache》一節中研究了fetchsize對應用內存的影響。網上關於fetchsize的文章不是很多,很難對這個參數有一個全面的瞭解,確實,如果從開發的角度來理解fetchsize,確實是存在一些困難的。那麼到時什麼是fetchsize?fetchsize對內存和性能到底有什麼影響呢?我在前段日子作了不少測試和研究,下面將自己的研究成果和大家分享一下。

1. 什麼是fetchsize?

1.1  Oracle中的fetchsize

先來簡單解釋一下,當我們執行一個SQL查詢語句的時候,需要在客戶端和服務器端都打開一個遊標,並且分別申請一塊內存空間,作爲存放查詢的數據的一個緩衝區。這塊內存區,存放多少條數據就由fetchsize來決定,同時每次網絡包會傳送fetchsize條記錄到客戶端。應該很容易理解,如果fetchsize設置爲20,當我們從服務器端查詢數據往客戶端傳送時,每次可以傳送20條數據,但是兩端分別需要20條數據的內存空閒來保存這些數據。fetchsize決定了每批次可以傳輸的記錄條數,但同時,也決定了內存的大小。這塊內存,在oracle服務器端是動態分配的(大家可以想想爲什麼)。而在客戶端(JBOSS),PS對象會存在一個緩衝中(LRU鏈表),也就是說,這塊內存是事先配好的,應用端內存的分配在conn.prepareStatement(sql)或都conn.CreateStatement(sql)的時候完成。

在java程序中,我們會執行以下代碼:

//打開遊標,執行查詢,但是並不獲取任何的數據,網絡上沒有數據的傳輸。
rs = stmt.executeQuery();
//獲取具體的數據,網絡一般每次傳輸fetchsize條數據。
while (rs.next()){
}

1.1  MYSQL中的fetchsize

MYSQL的preparestament基本上不佔用內存,爲什麼呢?因爲MYSQL並不需要象oracle那樣的一塊內存來保存結果集緩衝區,爲什麼不需要緩衝區,其中根本的原因是由MYSQL的通訊方式決定的。

MYSQL 客戶端/服務器協議是半雙工的,即MYSQL只能在給定的時間,發送或接受數據,但不能同時發送和接收。所以,MYSQL在數據查詢結果集傳送的時候,需要一次性將數據全部傳送到客戶端,在客戶數據接收完之後,釋放相關的鎖等資源。因爲這種半雙工的通訊方式,所以MYSQL不需要客戶端的遊標,但是客戶端API通過把結果取到內存中,可以模擬遊標的操作。所以,我們可以在JAVA程序中,可以象ORACLE那樣來實現MYSQL的訪問。

 

2. 如何設置fetchsize?

Fetchsize可以在任何一層進行設置 ,ORACLE JDBC驅動默認的FETCHSIZE爲10。一般爲了方便,我們會在數據源層面上來設置fetchsize。

2.1 語句級別的設置:

我們可以在jdbc中調用Preparedstatement .setFetchSize()的進行設置:

            stmt = conn.prepareStatement(sql);
            stmt.setFetchSize(50);

也可以在Ibatis, hibernate等框架上直接針對某個語句進行設置:

< select id="getAllProduct"> select * from employee < /select>

2.2 數據源中的全局設置

JBOSS連接中設置:

< connection-property name="defaultRowPrefetch">50</ connection-property>

2.3 Fetchsize的核心源碼:

可以在JDBC驅動類Oracle.jdbc.driver.OracleStatment中找到這個方法, setPrefetchInternal方法中傳入的默認值爲0,僞代碼如下:

void setPrefetchInternal(int paramInt){
         if (paramInt < 0) {
              DatabaseError.throwSqlException(68, "setFetchSize");
           }
		   //獲取連接池中的DefaultRowPrefetch屬性
        else if (paramInt == 0) {
              paramInt = this.connection.getDefaultRowPrefetch();
         }
       if (paramInt == this.defaultRowPrefetch)
             return;
          this.defaultRowPrefetch = paramInt;

       if ((this.currentResultSet == null) || (this.currentResultSet.closed)) {
            this.rowPrefetchChanged = true;
        }
}

3. Fetchsize對性能影響的測試:

3.1 空查詢結果集的測試:

查詢的表一共有300條記錄,測試中查詢的結果集爲空,執行的是全表掃描。

SQL> select count(*) from test10000; 

  COUNT(*)
----------
       300 

SQL> select * from test10000 where col_a='test'; 

no rows selected
數據庫 連接方式 PSCACHE fetchsize 字段長度 網絡距離 總記錄數 返回記錄 執行時間 (ms)
ORACLE oci 支持 1 10000 15KM 300 0 1.5875
ORACLE oci 支持 5 10000 15KM 300 0 1.5828
ORACLE oci 支持 10 10000 15KM 300 0 1.7781
ORACLE oci 支持 50 10000 15KM 300 0 2.0468
ORACLE oci 支持 100 10000 15KM 300 0 2.6656
ORACLE oci 支持 1 10000 本地 300 0 0.1646
ORACLE oci 支持 5 10000 本地 300 0 0.1713
ORACLE oci 支持 10 10000 本地 300 0 0.1898
ORACLE oci 支持 50 10000 本地 300 0 0.3431
ORACLE oci 支持 100 10000 本地 300 0 1.2609
ORACLE thin 支持 1 10000 15KM 300 0 1.6344
ORACLE thin 支持 10 10000 15KM 300 0 1.6687
ORACLE thin 支持 100 10000 15KM 300 0 1.6266
MYSQL jdbc 支持 1 10000 15KM 300 0 1.5187
MYSQL jdbc 支持 10 10000 15KM 300 0 1.6093
MYSQL jdbc 支持 100 10000 15KM 300 0 1.5906

 

從上面的測試中,可以得出如下結論:

  1. 在沒有記錄返回的情況下,OCI方式中fetchsize設置越大,對性能的影響越大。
  2. 在沒有記錄返回的情況下,THIN和mysql的方式中,fetchsize的大小,對於性能影響不大。

這是在空結果集情況下的影響,僅供參考,不應該作爲我們考慮的情況。

3.2 非空查詢結果集的測試:

數據庫 連接方式 PSCACHE fetchsize 字段長度 網絡距離 總記錄數 返回記錄 執行時間 (ms)
ORACLE oci 支持 1 100 15KM 300 300 226.9533
ORACLE oci 支持 5 100 15KM 300 300 86.44667
ORACLE oci 支持 10 100 15KM 300 300 43.74667
ORACLE oci 支持 50 100 15KM 300 300 10
ORACLE oci 支持 100 100 15KM 300 300 5.2
ORACLE oci 支持 1 100 15KM 300 10 8.44
ORACLE oci 支持 5 100 15KM 300 10 2.9
ORACLE oci 支持 10 100 15KM 300 10 1.56
ORACLE oci 支持 50 100 15KM 300 10 1.56
ORACLE oci 支持 1 100 本地 300 300 12.773
ORACLE oci 支持 5 100 本地 300 300 5.32
ORACLE oci 支持 10 100 本地 300 300 2.9

從上面的測試中,可以得出如下結論:

  1. 當返回結果集較大時,設置較大的fetchsize,對性能會有很大的提升。
  2. Fetchsize設置大於返回的記錄數時,對於性能的提升沒有任何的意義,反而會增加內存的開銷。

3.3 Fetchsize對性能和內存的影響,下面的圖可以很好的說明他們的關係:

3.4 Fetchsize和網絡的關係

當fetchsize設置到某一值時,便不會再有性能的提升,這不僅僅是因爲結果集大小的原因,和操作系統或者ORACLE上的TCP讀/寫的緩衝區也有關係:

–操作系統上控制網絡的讀/寫 buffer
–  net.core.rmem_default = 262144
–  net.core.wmem_default = 262144
–數據庫端控制,默認值爲操作系統上的設置 :
–  RECV_BUF_SIZE=9375000
-  SEND_BUF_SIZE=9375000

4. 總結:

fetchsize的設置,跟具體業務系統有關係,沒有一個最好的值可以供各個應用都可以使用。一般OLTP的系統,fetchsize使用jdbc的默認值就可以了。我查看了下網絡上的大部分文章,在某個特定的條件下測試的fetchsize,得出一個值,然後所有人都用這個值來設置自己的應用系統。一般情況下,這僅僅只是一些資源的浪費,但是,在某些情況下,如數據源拆分,讀寫分離架構中,當fetchsize設置的太大,有可能會導致性能的急劇下降,甚至會導致應用上可怕的JVM內存溢出,在不少公司發生過這種慘痛的教訓。建議在設置這個值之前,先做一個JVM內存的DUMP,以便能夠對內存的佔用情況有一個清晰的瞭解。

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