有些時候,咱們只是訪問表中的幾個字段,而且字段內容較少,咱們能夠爲這幾個字段單獨創建一個組合索引,這樣就能夠直接只經過訪問索引就能獲得數據,通常索引佔用的磁盤空間比表小不少,因此這種方式能夠大大減小磁盤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請求的字段都建在索引裏,因此這種只經過索引訪問數據的方法通常只用於核心應用,也就是那種對核心表訪問量最高且查詢字段數據量不多的查詢。性能優化
SQL執行計劃是關係型數據庫最核心的技術之一,它表示SQL執行時的數據訪問算法。因爲業務需求愈來愈複雜,表數據量也愈來愈大,程序員愈來愈懶惰,SQL也須要支持很是複雜的業務邏輯,但SQL的性能還須要提升,所以,優秀的關係型數據庫除了須要支持複雜的SQL語法及更多函數外,還須要有一套優秀的算法庫來提升SQL性能。服務器
目前ORACLE有SQL執行計劃的算法約300種,並且一直在增長,因此SQL執行計劃是一個很是複雜的課題,一個普通DBA能掌握50種就很不錯了,就算是資深DBA也不可能把每一個執行計劃的算法描述清楚。雖然有這麼多種算法,但並不表示咱們沒法優化執行計劃,由於咱們經常使用的SQL執行計劃算法也就十幾個,若是一個程序員能把這十幾個算法搞清楚,那就掌握了80%的SQL執行計劃調優知識。網絡
因爲篇幅的緣由,SQL執行計劃須要專題介紹,在這裏就很少說了。併發
通常數據分頁方式有:
將數據從應用服務器所有下載到本地應用程序或瀏覽器,在應用程序或瀏覽器內部經過本地代碼進行分頁處理
優勢:編碼簡單,減小客戶端與應用服務器網絡交互次數
缺點:首次交互時間長,佔用客戶端內存
適應場景:客戶端與應用服務器網絡延時較大,但要求後續操做流暢,如手機GPRS,超遠程訪問(跨國)等等。
將數據從數據庫服務器所有下載到應用服務器,在應用服務器內部再進行數據篩選。如下是一個應用服務器端Java程序分頁的示例:
List list=executeQuery(「select * from employee order by id」);
Int count= list.size();
List subList= list.subList(10, 20);
優勢:編碼簡單,只須要一次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個產品,假設訪問公司索引須要50個IO,2條記錄須要1個表數據IO。
那麼按第一種ROWNUM分頁寫法,須要550(50+1000/2)個IO,按第二種ROWID分頁寫法,只須要60個IO(50+20/2);
經過去除沒必要要的返回字段能夠提升性能,例:
調整前:select * from product where company_id=?;
調整後:select id,name from product where company_id=?;
優勢:
1、減小數據在網絡上傳輸開銷
2、減小服務器數據處理開銷
3、減小客戶端內存佔用
4、字段變動時提早發現問題,減小程序BUG
5、若是訪問的全部字段恰好在一個索引裏面,則可使用純索引訪問提升性能。
缺點:增長編碼工做量
因爲會增長一些編碼工做量,因此通常需求經過開發規範來要求程序員這麼作,不然等項目上線後再整改工做量更大。
若是你的查詢表中有大字段或內容較多的字段,如備註信息、文件內容等等,那在查詢表時必定要注意這方面的問題,不然可能會帶來嚴重的性能問題。若是表常常 要查詢而且請求大內容字段的機率很低,咱們能夠採用分表處理,將一個大表分拆成兩個一對一的關係表,將不經常使用的大內容字段放在一張單獨的表中。如一張存儲 上傳文件的表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
咱們能夠分拆成兩張一對一的關係表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
T_FILECONTENT(ID, FILE_CONTENT)
經過這種分拆,能夠大大提少T_FILE表的單條記錄及總大小,這樣在查詢T_FILE時性能會更好,當須要查詢FILE_CONTENT字段內容時再訪問T_FILECONTENT表。
數據庫訪問框架通常都提供了批量提交的接口,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倍性能,通常根據主鍵的Update或Delete操做也可能提升2-3倍性能,但不如Insert明顯,由於Update及Delete操做可能有比較大的開銷在物理IO訪問。以上僅是理論計算值,實際狀況須要根據具體環境測量。
不少時候咱們須要按一些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請求的數量,從而提升性能。那若是有10000個ID,那是否是所有放在一條SQL裏處理呢?答案確定是否認的。首先大部份數據庫都會有SQL長度和IN裏個數的限制,如ORACLE的IN裏就不容許超過1000個值。
另外當前數據庫通常都是採用基於成本的優化規則,當IN數量達到必定值時有可能改變SQL執行計劃,從索引訪問變成全表訪問,這將使性能急劇變化。隨着SQL中IN的裏面的值個數增長,SQL的執行計劃會更復雜,佔用的內存將會變大,這將會增長服務器CPU及內存成本。
評估在IN裏面一次放多少個值還須要考慮應用服務器本地內存的開銷,有併發訪問時要計算本地數據使用週期內的併發上限,不然可能會致使內存溢出。
綜合考慮,通常IN裏面的值個數超過20個之後性能基本沒什麼太大變化,也特別說明不要超過100,超事後可能會引發執行計劃的不穩定性及增長數據庫CPU及內存成本,這個須要專業DBA評估。
當咱們採用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_time(s) |
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,太大了也沒什麼性能提升,反而可能會增長內存溢出的危險。
注:圖中fetchsize在128之後會有一些小的波動,這並非測試偏差,而是因爲resultset填充到具體對像時間不一樣的緣由,因爲resultset已經到本地內存裏了,因此估計是因爲CPU的L1,L2 Cache命中率變化形成,因爲變化不大,因此筆者也未深刻分析緣由。
iBatis的SqlMapping配置文件能夠對每一個SQL語句指定fetchsize大小,以下所示: