數據庫訪問性能優化(二)

1.2、只經過索引訪問數據

有些時候,咱們只是訪問表中的幾個字段,而且字段內容較少,咱們能夠爲這幾個字段單獨創建一個組合索引,這樣就能夠直接只經過訪問索引就能獲得數據,通常索引佔用的磁盤空間比表小不少,因此這種方式能夠大大減小磁盤IO開銷。程序員

如:select id,name from company where type='2';算法

若是這個SQL常用,咱們能夠在type,id,name上建立組合索引sql

create index my_comb_index on company(type,id,name);數據庫

有了這個組合索引後,SQL就能夠直接經過my_comb_index索引返回數據,不須要訪問company表。瀏覽器

仍是拿字典舉例:有一個需求,須要查詢一本漢語字典中全部漢字的個數,若是咱們的字典沒有目錄索引,那咱們只能從字典內容裏一個一個字計數,最後返回結果。若是咱們有一個拼音目錄,那就能夠只訪問拼音目錄的漢字進行計數。若是一本字典有1000頁,拼音目錄有20頁,那咱們的數據訪問成本至關於全表訪問的50分之一。緩存

切記,性能優化是無止境的,當性能能夠知足需求時便可,不要過分優化。在實際數據庫中咱們不可能把每一個SQL請求的字段都建在索引裏,因此這種只經過索引訪問數據的方法通常只用於核心應用,也就是那種對核心表訪問量最高且查詢字段數據量不多的查詢。性能優化

1.3、優化SQL執行計劃

SQL執行計劃是關係型數據庫最核心的技術之一,它表示SQL執行時的數據訪問算法。因爲業務需求愈來愈複雜,表數據量也愈來愈大,程序員愈來愈懶惰,SQL也須要支持很是複雜的業務邏輯,但SQL的性能還須要提升,所以,優秀的關係型數據庫除了須要支持複雜的SQL語法及更多函數外,還須要有一套優秀的算法庫來提升SQL性能。服務器

目前ORACLESQL執行計劃的算法約300種,並且一直在增長,因此SQL執行計劃是一個很是複雜的課題,一個普通DBA能掌握50種就很不錯了,就算是資深DBA也不可能把每一個執行計劃的算法描述清楚。雖然有這麼多種算法,但並不表示咱們沒法優化執行計劃,由於咱們經常使用的SQL執行計劃算法也就十幾個,若是一個程序員能把這十幾個算法搞清楚,那就掌握了80%SQL執行計劃調優知識。網絡

因爲篇幅的緣由,SQL執行計劃須要專題介紹,在這裏就很少說了。併發

 

2、返回更少的數據

2.1、數據分頁處理

通常數據分頁方式有:

2.1.1、客戶端(應用程序或瀏覽器)分頁

將數據從應用服務器所有下載到本地應用程序或瀏覽器,在應用程序或瀏覽器內部經過本地代碼進行分頁處理

優勢:編碼簡單,減小客戶端與應用服務器網絡交互次數

缺點:首次交互時間長,佔用客戶端內存

適應場景:客戶端與應用服務器網絡延時較大,但要求後續操做流暢,如手機GPRS,超遠程訪問(跨國)等等。

2.1.2、應用服務器分頁

將數據從數據庫服務器所有下載到應用服務器,在應用服務器內部再進行數據篩選。如下是一個應用服務器端Java程序分頁的示例:

List list=executeQuery(「select * from employee order by id」);

Int count= list.size();

List subList= list.subList(10, 20);

 

優勢:編碼簡單,只須要一次SQL交互,總數據與分頁數據差很少時性能較好。

缺點:總數據量較多時性能較差。

適應場景:數據庫系統不支持分頁處理,數據量較小而且可控。

 

2.1.3、數據庫SQL分頁

採用數據庫SQL分頁須要兩次SQL完成

一個SQL計算總數量

一個SQL返回分頁後的數據

優勢:性能好

缺點:編碼複雜,各類數據庫語法不一樣,須要兩次SQL交互。

 

oracle數據庫通常採用rownum來進行分頁,經常使用分頁語法有以下兩種:

 

直接經過rownum分頁:

select * from (

         select a.*,rownum rn from

                   (select * from product a where company_id=? order by status) a

         where rownum<=20)

where rn>10;

數據訪問開銷=索引IO+索引所有記錄結果對應的表數據IO

 

採用rowid分頁語法

優化原理是經過純索引找出分頁記錄的ROWID,再經過ROWID回表返回數據,要求內層查詢和排序字段全在索引裏。

create index myindex on product(company_id,status);

 

select b.* from (

         select * from (

                   select a.*,rownum rn from

                            (select rowid rid,status from product a where company_id=? order by status) a

                   where rownum<=20)

         where rn>10) a, product b

where a.rid=b.rowid;

數據訪問開銷=索引IO+索引分頁結果對應的表數據IO

 

實例:

一個公司產品有1000條記錄,要分頁取其中20個產品,假設訪問公司索引須要50IO2條記錄須要1個表數據IO

那麼按第一種ROWNUM分頁寫法,須要550(50+1000/2)IO,按第二種ROWID分頁寫法,只須要60IO(50+20/2);

 

2.2、只返回須要的字段

經過去除沒必要要的返回字段能夠提升性能,例:

調整前:select * from product where company_id=?;

調整後:select id,name from product where company_id=?;

 

優勢:

1、減小數據在網絡上傳輸開銷

2、減小服務器數據處理開銷

3、減小客戶端內存佔用

4、字段變動時提早發現問題,減小程序BUG

5、若是訪問的全部字段恰好在一個索引裏面,則可使用純索引訪問提升性能。

缺點:增長編碼工做量

因爲會增長一些編碼工做量,因此通常需求經過開發規範來要求程序員這麼作,不然等項目上線後再整改工做量更大。

若是你的查詢表中有大字段或內容較多的字段,如備註信息、文件內容等等,那在查詢表時必定要注意這方面的問題,不然可能會帶來嚴重的性能問題。若是表常常 要查詢而且請求大內容字段的機率很低,咱們能夠採用分表處理,將一個大表分拆成兩個一對一的關係表,將不經常使用的大內容字段放在一張單獨的表中。如一張存儲 上傳文件的表:

T_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT

咱們能夠分拆成兩張一對一的關係表:

T_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE

T_FILECONTENTID, FILE_CONTENT

         經過這種分拆,能夠大大提少T_FILE表的單條記錄及總大小,這樣在查詢T_FILE時性能會更好,當須要查詢FILE_CONTENT字段內容時再訪問T_FILECONTENT表。

 

3、減小交互次數

3.1batch DML

數據庫訪問框架通常都提供了批量提交的接口,jdbc支持batch的提交處理方法,當你一次性要往一個表中插入1000萬條數據時,若是採用普通的executeUpdate處理,那麼和服務器交互次數爲1000萬次,按每秒鐘能夠向數據庫服務器提交10000次估算,要完成全部工做須要1000秒。若是採用批量提交模式,1000條提交一次,那麼和服務器交互次數爲1萬次,交互次數大大減小。採用batch操做通常不會減小不少數據庫服務器的物理IO,可是會大大減小客戶端與服務端的交互次數,從而減小了屢次發起的網絡延時開銷,同時也會下降數據庫的CPU開銷。

 

假設要向一個普通表插入1000萬數據,每條記錄大小爲1K字節,表上沒有任何索引,客戶端與數據庫服務器網絡是100Mbps,如下是根據如今通常計算機能力估算的各類batch大小性能對比值:

 

 

 單位:ms

No batch

Batch=10

Batch=100

Batch=1000

Batch=10000

服務器事務處理時間

0.1

0.1

0.1

0.1

0.1

服務器IO處理時間

0.02

0.2

2

20

200

網絡交互發起時間

0.1

0.1

0.1

0.1

0.1

網絡數據傳輸時間

0.01

0.1

1

10

100

小計

0.23

0.5

3.2

30.2

300.2

平均每條記錄處理時間

0.23

0.05

0.032

0.0302

0.03002

 

 

從上能夠看出,Insert操做加大Batch能夠對性能提升近8倍性能,通常根據主鍵的UpdateDelete操做也可能提升2-3倍性能,但不如Insert明顯,由於UpdateDelete操做可能有比較大的開銷在物理IO訪問。以上僅是理論計算值,實際狀況須要根據具體環境測量。

 

3.2In List

不少時候咱們須要按一些ID查詢數據庫記錄,咱們能夠採用一個ID一個請求發給數據庫,以下所示:

for :var in ids[] do begin

  select * from mytable where id=:var;

end;

 

咱們也能夠作一個小的優化, 以下所示,用ID INLIST的這種方式寫SQL

select * from mytable where id in(:id1,id2,...,idn);

 

經過這樣處理能夠大大減小SQL請求的數量,從而提升性能。那若是有10000ID,那是否是所有放在一條SQL裏處理呢?答案確定是否認的。首先大部份數據庫都會有SQL長度和IN裏個數的限制,如ORACLEIN裏就不容許超過1000個值

另外當前數據庫通常都是採用基於成本的優化規則,當IN數量達到必定值時有可能改變SQL執行計劃,從索引訪問變成全表訪問,這將使性能急劇變化。隨着SQLIN的裏面的值個數增長,SQL的執行計劃會更復雜,佔用的內存將會變大,這將會增長服務器CPU及內存成本。

評估在IN裏面一次放多少個值還須要考慮應用服務器本地內存的開銷,有併發訪問時要計算本地數據使用週期內的併發上限,不然可能會致使內存溢出。

綜合考慮,通常IN裏面的值個數超過20個之後性能基本沒什麼太大變化,也特別說明不要超過100,超事後可能會引發執行計劃的不穩定性及增長數據庫CPU及內存成本,這個須要專業DBA評估。

 

3.3、設置Fetch Size

當咱們採用select從數據庫查詢數據時,數據默認並非一條一條返回給客戶端的,也不是一次所有返回客戶端的,而是根據客戶端fetch_size參數處理,每次只返回fetch_size條記錄,當客戶端遊標遍歷到尾部時再從服務端取數據,直到最後所有傳送完成。因此若是咱們要從服務端一次取大量數據時,能夠加大fetch_size,這樣能夠減小結果數據傳輸的交互次數及服務器數據準備時間,提升性能。

 

如下是jdbc測試的代碼,採用本地數據庫,表緩存在數據庫CACHE中,所以沒有網絡鏈接及磁盤IO開銷,客戶端只遍歷遊標,不作任何處理,這樣更能體現fetch參數的影響:

String vsql ="select * from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(1000);

ResultSet rs = pstmt.executeQuery(vsql);

int cnt = rs.getMetaData().getColumnCount();

Object o;

while (rs.next()) {

    for (int i = 1; i <= cnt; i++) {

       o = rs.getObject(i);

    }

}

 

測試示例中的employee表有100000條記錄,每條記錄平均長度135字節

 

如下是測試結果,對每種fetchsize測試5次再取平均值:

 

fetchsize

 elapse_times

1

20.516

2

11.34

4

6.894

8

4.65

16

3.584

32

2.865

64

2.656

128

2.44

256

2.765

512

3.075

1024

2.862

2048

2.722

4096

2.681

8192

2.715

 

 

 

數據庫訪問性能優化(二) 

Oracle jdbc fetchsize默認值爲10,由上測試能夠看出fetchsize對性能影響仍是比較大的,可是當fetchsize大於100時就基本上沒有影響了。fetchsize並不會存在一個最優的固定值,由於總體性能與記錄集大小及硬件平臺有關。根據測試結果建議當一次性要取大量數據時這個值設置爲100左右,不要小於40。注意,fetchsize不能設置太大,若是一次取出的數據大於JVM的內存會致使內存溢出,因此建議不要超過1000,太大了也沒什麼性能提升,反而可能會增長內存溢出的危險。

注:圖中fetchsize128之後會有一些小的波動,這並非測試偏差,而是因爲resultset填充到具體對像時間不一樣的緣由,因爲resultset已經到本地內存裏了,因此估計是因爲CPUL1,L2 Cache命中率變化形成,因爲變化不大,因此筆者也未深刻分析緣由。

 

iBatisSqlMapping配置文件能夠對每一個SQL語句指定fetchsize大小,以下所示:

相關文章
相關標籤/搜索