轉(zhuǎn)載自:https://www.cnblogs.com/easypass/archive/2010/12/08/1900127.html
IO各層次性能
從上圖可以看出肃续,計(jì)算機(jī)系統(tǒng)硬件性能從高到代依次為:
CPU——Cache(L1-L2-L3)——內(nèi)存——SSD硬盤——網(wǎng)絡(luò)——硬盤
數(shù)據(jù)優(yōu)化的五個(gè)法則:
1褐荷、減少磁盤訪問
2跪削、返回更少數(shù)據(jù)
3惶傻、減少交互次數(shù)
4缎除、減少CPU和內(nèi)存開銷
5爬立、增加資源
1囱桨、減少數(shù)據(jù)訪問
1.1、創(chuàng)建并使用正確的索引
數(shù)據(jù)庫(kù)索引的原理非常簡(jiǎn)單秀撇,但在復(fù)雜的表中真正能正確使用索引的人很少超棺,即使是專業(yè)的DBA也不一定能完全做到最優(yōu)。
索引會(huì)大大增加表記錄的DML(INSERT,UPDATE,DELETE)開銷呵燕,正確的索引可以讓性能提升100棠绘,1000倍以上,不合理的索引也可能會(huì)讓性能下降100倍,因此在一個(gè)表中創(chuàng)建什么樣的索引需要平衡各種業(yè)務(wù)需求。
索引常見問題:
索引有哪些種類会通?
常見的索引有B-TREE索引疾忍、位圖索引、全文索引盅弛,位圖索引一般用于數(shù)據(jù)倉(cāng)庫(kù)應(yīng)用,全文索引由于使用較少,這里不深入介紹赡突。B-TREE索引包括很多擴(kuò)展類型对扶,如組合索引、反向索引惭缰、函數(shù)索引等等浪南,以下是B-TREE索引的簡(jiǎn)單介紹:
B-TREE索引也稱為平衡樹索引(Balance Tree),它是一種按字段排好序的樹形目錄結(jié)構(gòu)漱受,主要用于提升查詢性能和唯一約束支持络凿。B-TREE索引的內(nèi)容包括根節(jié)點(diǎn)、分支節(jié)點(diǎn)昂羡、葉子節(jié)點(diǎn)絮记。
葉子節(jié)點(diǎn)內(nèi)容:索引字段內(nèi)容+表記錄ROWID
根節(jié)點(diǎn),分支節(jié)點(diǎn)內(nèi)容:當(dāng)一個(gè)數(shù)據(jù)塊中不能放下所有索引字段數(shù)據(jù)時(shí)虐先,就會(huì)形成樹形的根節(jié)點(diǎn)或分支節(jié)點(diǎn)到千,根節(jié)點(diǎn)與分支節(jié)點(diǎn)保存了索引樹的順序及各層級(jí)間的引用關(guān)系。
?????????一個(gè)普通的BTREE索引結(jié)構(gòu)示意圖如下所示:
如果我們把一個(gè)表的內(nèi)容認(rèn)為是一本字典赴穗,那索引就相當(dāng)于字典的目錄憔四,如下圖所示:
圖中是一個(gè)字典按部首+筆劃數(shù)的目錄,相當(dāng)于給字典建了一個(gè)按部首+筆劃的組合索引般眉。
一個(gè)表中可以建多個(gè)索引了赵,就如一本字典可以建多個(gè)目錄一樣(按拼音、筆劃甸赃、部首等等)柿汛。
一個(gè)索引也可以由多個(gè)字段組成,稱為組合索引埠对,如上圖就是一個(gè)按部首+筆劃的組合目錄络断。
SQL什么條件會(huì)使用索引?
當(dāng)字段上建有索引時(shí)项玛,通常以下情況會(huì)使用索引:
INDEX_COLUMN = ?
INDEX_COLUMN > ?
INDEX_COLUMN >= ?
INDEX_COLUMN < ?
INDEX_COLUMN <= ?
INDEX_COLUMN between ? and ?
INDEX_COLUMN in (?,?,...,?)
INDEX_COLUMN like ?||'%'(后導(dǎo)模糊查詢)
T1. INDEX_COLUMN=T2. COLUMN1(兩個(gè)表通過索引字段關(guān)聯(lián))
SQL什么條件不會(huì)使用索引貌笨?
查詢條件不能使用索引原因
INDEX_COLUMN <> ?
INDEX_COLUMN not in (?,?,...,?)
不等于操作不能使用索引
function(INDEX_COLUMN) = ?
INDEX_COLUMN + 1 = ?
INDEX_COLUMN || 'a' = ?
經(jīng)過普通運(yùn)算或函數(shù)運(yùn)算后的索引字段不能使用索引
INDEX_COLUMN like '%'||?
INDEX_COLUMN like '%'||?||'%'
含前導(dǎo)模糊查詢的Like語法不能使用索引
INDEX_COLUMN is nullB-TREE索引里不保存字段為NULL值記錄,因此IS NULL不能使用索引
NUMBER_INDEX_COLUMN='12345'
CHAR_INDEX_COLUMN=12345
Oracle在做數(shù)值比較時(shí)需要將兩邊的數(shù)據(jù)轉(zhuǎn)換成同一種數(shù)據(jù)類型襟沮,如果兩邊數(shù)據(jù)類型不同時(shí)會(huì)對(duì)字段值隱式轉(zhuǎn)換锥惋,相當(dāng)于加了一層函數(shù)處理,所以不能使用索引开伏。
a.INDEX_COLUMN=a.COLUMN_1給索引查詢的值應(yīng)是已知數(shù)據(jù)膀跌,不能是未知字段值。
注:
經(jīng)過函數(shù)運(yùn)算字段的字段要使用可以使用函數(shù)索引固灵,這種需求建議與DBA溝通捅伤。
有時(shí)候我們會(huì)使用多個(gè)字段的組合索引,如果查詢條件中第一個(gè)字段不能使用索引巫玻,那整個(gè)查詢也不能使用索引
如:我們company表建了一個(gè)id+name的組合索引丛忆,以下SQL是不能使用索引的
Select * from company where name=?
Oracle9i后引入了一種index skip scan的索引方式來解決類似的問題困介,但是通過index skip scan提高性能的條件比較特殊,使用不好反而性能會(huì)更差蘸际。
我們一般在什么字段上建索引?
這是一個(gè)非常復(fù)雜的話題徒扶,需要對(duì)業(yè)務(wù)及數(shù)據(jù)充分分析后再能得出結(jié)果粮彤。主鍵及外鍵通常都要有索引,其它需要建索引的字段應(yīng)滿足以下條件:
1姜骡、字段出現(xiàn)在查詢條件中导坟,并且查詢條件可以使用索引;
2圈澈、語句執(zhí)行頻率高惫周,一天會(huì)有幾千次以上;
3康栈、通過字段條件可篩選的記錄集很小递递,那數(shù)據(jù)篩選比例是多少才適合?
這個(gè)沒有固定值啥么,需要根據(jù)表數(shù)據(jù)量來評(píng)估登舞,以下是經(jīng)驗(yàn)公式,可用于快速評(píng)估:
小表(記錄數(shù)小于10000行的表):篩選比例<10%悬荣;
大表:(篩選返回記錄數(shù))<(表總記錄數(shù)*單條記錄長(zhǎng)度)/10000/16
??????單條記錄長(zhǎng)度≈字段平均內(nèi)容長(zhǎng)度之和+字段數(shù)*2
以下是一些字段是否需要建B-TREE索引的經(jīng)驗(yàn)分類:
?字段類型常見字段名
需要建索引的字段主鍵ID,PK
外鍵PRODUCT_ID,COMPANY_ID,MEMBER_ID,ORDER_ID,TRADE_ID,PAY_ID
有對(duì)像或身份標(biāo)識(shí)意義字段HASH_CODE,USERNAME,IDCARD_NO,EMAIL,TEL_NO,IM_NO
索引慎用字段,需要進(jìn)行數(shù)據(jù)分布及使用場(chǎng)景詳細(xì)評(píng)估日期GMT_CREATE,GMT_MODIFIED
年月YEAR,MONTH
狀態(tài)標(biāo)志PRODUCT_STATUS,ORDER_STATUS,IS_DELETE,VIP_FLAG
類型ORDER_TYPE,IMAGE_TYPE,GENDER,CURRENCY_TYPE
區(qū)域COUNTRY,PROVINCE,CITY
操作人員CREATOR,AUDITOR
數(shù)值LEVEL,AMOUNT,SCORE
長(zhǎng)字符ADDRESS,COMPANY_NAME,SUMMARY,SUBJECT
不適合建索引的字段描述備注DESCRIPTION,REMARK,MEMO,DETAIL
大字段FILE_CONTENT,EMAIL_CONTENT
如何知道SQL是否使用了正確的索引菠秒?
簡(jiǎn)單SQL可以根據(jù)索引使用語法規(guī)則判斷,復(fù)雜的SQL不好辦氯迂,判斷SQL的響應(yīng)時(shí)間是一種策略践叠,但是這會(huì)受到數(shù)據(jù)量、主機(jī)負(fù)載及緩存等因素的影響嚼蚀,有時(shí)數(shù)據(jù)全在緩存里禁灼,可能全表訪問的時(shí)間比索引訪問時(shí)間還少。要準(zhǔn)確知道索引是否正確使用轿曙,需要到數(shù)據(jù)庫(kù)中查看SQL真實(shí)的執(zhí)行計(jì)劃匾二,這個(gè)話題比較復(fù)雜,詳見SQL執(zhí)行計(jì)劃專題介紹拳芙。
索引對(duì)DML(INSERT,UPDATE,DELETE)附加的開銷有多少察藐?
這個(gè)沒有固定的比例,與每個(gè)表記錄的大小及索引字段大小密切相關(guān)舟扎,以下是一個(gè)普通表測(cè)試數(shù)據(jù)分飞,僅供參考:
索引對(duì)于Insert性能降低56%
索引對(duì)于Update性能降低47%
索引對(duì)于Delete性能降低29%
因此對(duì)于寫IO壓力比較大的系統(tǒng),表的索引需要仔細(xì)評(píng)估必要性睹限,另外索引也會(huì)占用一定的存儲(chǔ)空間譬猫。
1.2讯檐、只通過索引訪問數(shù)據(jù)
有些時(shí)候,我們只是訪問表中的幾個(gè)字段染服,并且字段內(nèi)容較少别洪,我們可以為這幾個(gè)字段單獨(dú)建立一個(gè)組合索引,這樣就可以直接只通過訪問索引就能得到數(shù)據(jù)柳刮,一般索引占用的磁盤空間比表小很多挖垛,所以這種方式可以大大減少磁盤IO開銷。
如:select id,name from company where type='2';
如果這個(gè)SQL經(jīng)常使用秉颗,我們可以在type,id,name上創(chuàng)建組合索引
create index my_comb_index on company(type,id,name);
有了這個(gè)組合索引后痢毒,SQL就可以直接通過my_comb_index索引返回?cái)?shù)據(jù),不需要訪問company表蚕甥。
還是拿字典舉例:有一個(gè)需求哪替,需要查詢一本漢語字典中所有漢字的個(gè)數(shù),如果我們的字典沒有目錄索引菇怀,那我們只能從字典內(nèi)容里一個(gè)一個(gè)字計(jì)數(shù)凭舶,最后返回結(jié)果。如果我們有一個(gè)拼音目錄爱沟,那就可以只訪問拼音目錄的漢字進(jìn)行計(jì)數(shù)库快。如果一本字典有1000頁,拼音目錄有20頁钥顽,那我們的數(shù)據(jù)訪問成本相當(dāng)于全表訪問的50分之一义屏。
切記,性能優(yōu)化是無止境的蜂大,當(dāng)性能可以滿足需求時(shí)即可闽铐,不要過度優(yōu)化。在實(shí)際數(shù)據(jù)庫(kù)中我們不可能把每個(gè)SQL請(qǐng)求的字段都建在索引里奶浦,所以這種只通過索引訪問數(shù)據(jù)的方法一般只用于核心應(yīng)用兄墅,也就是那種對(duì)核心表訪問量最高且查詢字段數(shù)據(jù)量很少的查詢。
1.3澳叉、優(yōu)化SQL執(zhí)行計(jì)劃
SQL執(zhí)行計(jì)劃是關(guān)系型數(shù)據(jù)庫(kù)最核心的技術(shù)之一隙咸,它表示SQL執(zhí)行時(shí)的數(shù)據(jù)訪問算法。由于業(yè)務(wù)需求越來越復(fù)雜成洗,表數(shù)據(jù)量也越來越大五督,程序員越來越懶惰,SQL也需要支持非常復(fù)雜的業(yè)務(wù)邏輯瓶殃,但SQL的性能還需要提高充包,因此,優(yōu)秀的關(guān)系型數(shù)據(jù)庫(kù)除了需要支持復(fù)雜的SQL語法及更多函數(shù)外遥椿,還需要有一套優(yōu)秀的算法庫(kù)來提高SQL性能基矮。
目前ORACLE有SQL執(zhí)行計(jì)劃的算法約300種淆储,而且一直在增加,所以SQL執(zhí)行計(jì)劃是一個(gè)非常復(fù)雜的課題家浇,一個(gè)普通DBA能掌握50種就很不錯(cuò)了本砰,就算是資深DBA也不可能把每個(gè)執(zhí)行計(jì)劃的算法描述清楚。雖然有這么多種算法钢悲,但并不表示我們無法優(yōu)化執(zhí)行計(jì)劃点额,因?yàn)槲覀兂S玫腟QL執(zhí)行計(jì)劃算法也就十幾個(gè),如果一個(gè)程序員能把這十幾個(gè)算法搞清楚譬巫,那就掌握了80%的SQL執(zhí)行計(jì)劃調(diào)優(yōu)知識(shí)。
由于篇幅的原因督笆,SQL執(zhí)行計(jì)劃需要專題介紹芦昔,在這里就不多說了。
2娃肿、返回更少的數(shù)據(jù)
2.1咕缎、數(shù)據(jù)分頁處理
一般數(shù)據(jù)分頁方式有:
2.1.1、客戶端(應(yīng)用程序或?yàn)g覽器)分頁
將數(shù)據(jù)從應(yīng)用服務(wù)器全部下載到本地應(yīng)用程序或?yàn)g覽器料扰,在應(yīng)用程序或?yàn)g覽器內(nèi)部通過本地代碼進(jìn)行分頁處理
優(yōu)點(diǎn):編碼簡(jiǎn)單凭豪,減少客戶端與應(yīng)用服務(wù)器網(wǎng)絡(luò)交互次數(shù)
缺點(diǎn):首次交互時(shí)間長(zhǎng),占用客戶端內(nèi)存
適應(yīng)場(chǎng)景:客戶端與應(yīng)用服務(wù)器網(wǎng)絡(luò)延時(shí)較大晒杈,但要求后續(xù)操作流暢嫂伞,如手機(jī)GPRS,超遠(yuǎn)程訪問(跨國(guó))等等拯钻。
2.1.2帖努、應(yīng)用服務(wù)器分頁
將數(shù)據(jù)從數(shù)據(jù)庫(kù)服務(wù)器全部下載到應(yīng)用服務(wù)器,在應(yīng)用服務(wù)器內(nèi)部再進(jìn)行數(shù)據(jù)篩選粪般。以下是一個(gè)應(yīng)用服務(wù)器端Java程序分頁的示例:
List list=executeQuery(“select * from employee order by id”);
Int count= list.size();
List subList= list.subList(10, 20);
優(yōu)點(diǎn):編碼簡(jiǎn)單拼余,只需要一次SQL交互,總數(shù)據(jù)與分頁數(shù)據(jù)差不多時(shí)性能較好亩歹。
缺點(diǎn):總數(shù)據(jù)量較多時(shí)性能較差匙监。
適應(yīng)場(chǎng)景:數(shù)據(jù)庫(kù)系統(tǒng)不支持分頁處理,數(shù)據(jù)量較小并且可控小作。
2.1.3亭姥、數(shù)據(jù)庫(kù)SQL分頁
采用數(shù)據(jù)庫(kù)SQL分頁需要兩次SQL完成
一個(gè)SQL計(jì)算總數(shù)量
一個(gè)SQL返回分頁后的數(shù)據(jù)
優(yōu)點(diǎn):性能好
缺點(diǎn):編碼復(fù)雜,各種數(shù)據(jù)庫(kù)語法不同顾稀,需要兩次SQL交互致份。
oracle數(shù)據(jù)庫(kù)一般采用rownum來進(jìn)行分頁,常用分頁語法有如下兩種:
直接通過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;
數(shù)據(jù)訪問開銷=索引IO+索引全部記錄結(jié)果對(duì)應(yīng)的表數(shù)據(jù)IO
采用rowid分頁語法
優(yōu)化原理是通過純索引找出分頁記錄的ROWID础拨,再通過ROWID回表返回?cái)?shù)據(jù)氮块,要求內(nèi)層查詢和排序字段全在索引里绍载。
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;
數(shù)據(jù)訪問開銷=索引IO+索引分頁結(jié)果對(duì)應(yīng)的表數(shù)據(jù)IO
實(shí)例:
一個(gè)公司產(chǎn)品有1000條記錄,要分頁取其中20個(gè)產(chǎn)品滔蝉,假設(shè)訪問公司索引需要50個(gè)IO击儡,2條記錄需要1個(gè)表數(shù)據(jù)IO。
那么按第一種ROWNUM分頁寫法蝠引,需要550(50+1000/2)個(gè)IO阳谍,按第二種ROWID分頁寫法,只需要60個(gè)IO(50+20/2);
2.2螃概、只返回需要的字段
通過去除不必要的返回字段可以提高性能矫夯,例:
調(diào)整前:select * from product where company_id=?;
調(diào)整后:select id,name from product where company_id=?;
優(yōu)點(diǎn):
1、減少數(shù)據(jù)在網(wǎng)絡(luò)上傳輸開銷
2吊洼、減少服務(wù)器數(shù)據(jù)處理開銷
3训貌、減少客戶端內(nèi)存占用
4、字段變更時(shí)提前發(fā)現(xiàn)問題冒窍,減少程序BUG
5递沪、如果訪問的所有字段剛好在一個(gè)索引里面,則可以使用純索引訪問提高性能综液。
缺點(diǎn):增加編碼工作量
由于會(huì)增加一些編碼工作量款慨,所以一般需求通過開發(fā)規(guī)范來要求程序員這么做,否則等項(xiàng)目上線后再整改工作量更大谬莹。
如果你的查詢表中有大字段或內(nèi)容較多的字段檩奠,如備注信息、文件內(nèi)容等等附帽,那在查詢表時(shí)一定要注意這方面的問題笆凌,否則可能會(huì)帶來嚴(yán)重的性能問題。如果表經(jīng)常要查詢并且請(qǐng)求大內(nèi)容字段的概率很低士葫,我們可以采用分表處理乞而,將一個(gè)大表分拆成兩個(gè)一對(duì)一的關(guān)系表,將不常用的大內(nèi)容字段放在一張單獨(dú)的表中慢显。如一張存儲(chǔ)上傳文件的表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
我們可以分拆成兩張一對(duì)一的關(guān)系表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
T_FILECONTENT(ID, FILE_CONTENT)
?????????通過這種分拆爪模,可以大大提少T_FILE表的單條記錄及總大小,這樣在查詢T_FILE時(shí)性能會(huì)更好荚藻,當(dāng)需要查詢FILE_CONTENT字段內(nèi)容時(shí)再訪問T_FILECONTENT表
3屋灌、減少交互次數(shù)
3.1、batch DML
數(shù)據(jù)庫(kù)訪問框架一般都提供了批量提交的接口应狱,jdbc支持batch的提交處理方法共郭,當(dāng)你一次性要往一個(gè)表中插入1000萬條數(shù)據(jù)時(shí),如果采用普通的executeUpdate處理,那么和服務(wù)器交互次數(shù)為1000萬次除嘹,按每秒鐘可以向數(shù)據(jù)庫(kù)服務(wù)器提交10000次估算写半,要完成所有工作需要1000秒。如果采用批量提交模式尉咕,1000條提交一次叠蝇,那么和服務(wù)器交互次數(shù)為1萬次,交互次數(shù)大大減少年缎。采用batch操作一般不會(huì)減少很多數(shù)據(jù)庫(kù)服務(wù)器的物理IO悔捶,但是會(huì)大大減少客戶端與服務(wù)端的交互次數(shù),從而減少了多次發(fā)起的網(wǎng)絡(luò)延時(shí)開銷单芜,同時(shí)也會(huì)降低數(shù)據(jù)庫(kù)的CPU開銷蜕该。
假設(shè)要向一個(gè)普通表插入1000萬數(shù)據(jù),每條記錄大小為1K字節(jié)洲鸠,表上沒有任何索引堂淡,客戶端與數(shù)據(jù)庫(kù)服務(wù)器網(wǎng)絡(luò)是100Mbps,以下是根據(jù)現(xiàn)在一般計(jì)算機(jī)能力估算的各種batch大小性能對(duì)比值:
從上可以看出坛怪,Insert操作加大Batch可以對(duì)性能提高近8倍性能淤齐,一般根據(jù)主鍵的Update或Delete操作也可能提高2-3倍性能股囊,但不如Insert明顯袜匿,因?yàn)閁pdate及Delete操作可能有比較大的開銷在物理IO訪問。以上僅是理論計(jì)算值稚疹,實(shí)際情況需要根據(jù)具體環(huán)境測(cè)量居灯。
3.2、In List
很多時(shí)候我們需要按一些ID查詢數(shù)據(jù)庫(kù)記錄内狗,我們可以采用一個(gè)ID一個(gè)請(qǐng)求發(fā)給數(shù)據(jù)庫(kù)怪嫌,如下所示:
for :var in ids[] do begin
??select * from mytable where id=:var;
end;
我們也可以做一個(gè)小的優(yōu)化,如下所示柳沙,用ID INLIST的這種方式寫SQL:
select * from mytable where id in(:id1,id2,...,idn);
通過這樣處理可以大大減少SQL請(qǐng)求的數(shù)量岩灭,從而提高性能。那如果有10000個(gè)ID赂鲤,那是不是全部放在一條SQL里處理呢噪径?答案肯定是否定的。首先大部份數(shù)據(jù)庫(kù)都會(huì)有SQL長(zhǎng)度和IN里個(gè)數(shù)的限制数初,如ORACLE的IN里就不允許超過1000個(gè)值找爱。
另外當(dāng)前數(shù)據(jù)庫(kù)一般都是采用基于成本的優(yōu)化規(guī)則,當(dāng)IN數(shù)量達(dá)到一定值時(shí)有可能改變SQL執(zhí)行計(jì)劃泡孩,從索引訪問變成全表訪問车摄,這將使性能急劇變化茎杂。隨著SQL中IN的里面的值個(gè)數(shù)增加,SQL的執(zhí)行計(jì)劃會(huì)更復(fù)雜诺凡,占用的內(nèi)存將會(huì)變大病瞳,這將會(huì)增加服務(wù)器CPU及內(nèi)存成本。
評(píng)估在IN里面一次放多少個(gè)值還需要考慮應(yīng)用服務(wù)器本地內(nèi)存的開銷薄料,有并發(fā)訪問時(shí)要計(jì)算本地?cái)?shù)據(jù)使用周期內(nèi)的并發(fā)上限敞贡,否則可能會(huì)導(dǎo)致內(nèi)存溢出。
綜合考慮摄职,一般IN里面的值個(gè)數(shù)超過20個(gè)以后性能基本沒什么太大變化誊役,也特別說明不要超過100,超過后可能會(huì)引起執(zhí)行計(jì)劃的不穩(wěn)定性及增加數(shù)據(jù)庫(kù)CPU及內(nèi)存成本谷市,這個(gè)需要專業(yè)DBA評(píng)估蛔垢。
3.3、設(shè)置Fetch Size
當(dāng)我們采用select從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)時(shí)迫悠,數(shù)據(jù)默認(rèn)并不是一條一條返回給客戶端的鹏漆,也不是一次全部返回客戶端的,而是根據(jù)客戶端fetch_size參數(shù)處理创泄,每次只返回fetch_size條記錄艺玲,當(dāng)客戶端游標(biāo)遍歷到尾部時(shí)再?gòu)姆?wù)端取數(shù)據(jù),直到最后全部傳送完成鞠抑。所以如果我們要從服務(wù)端一次取大量數(shù)據(jù)時(shí)饭聚,可以加大fetch_size,這樣可以減少結(jié)果數(shù)據(jù)傳輸?shù)慕换ゴ螖?shù)及服務(wù)器數(shù)據(jù)準(zhǔn)備時(shí)間搁拙,提高性能秒梳。
以下是jdbc測(cè)試的代碼,采用本地?cái)?shù)據(jù)庫(kù)箕速,表緩存在數(shù)據(jù)庫(kù)CACHE中酪碘,因此沒有網(wǎng)絡(luò)連接及磁盤IO開銷,客戶端只遍歷游標(biāo)盐茎,不做任何處理兴垦,這樣更能體現(xiàn)fetch參數(shù)的影響:
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);
????}
}
測(cè)試示例中的employee表有100000條記錄,每條記錄平均長(zhǎng)度135字節(jié)
以下是測(cè)試結(jié)果字柠,對(duì)每種fetchsize測(cè)試5次再取平均值:
Oracle jdbc fetchsize默認(rèn)值為10探越,由上測(cè)試可以看出fetchsize對(duì)性能影響還是比較大的,但是當(dāng)fetchsize大于100時(shí)就基本上沒有影響了募谎。fetchsize并不會(huì)存在一個(gè)最優(yōu)的固定值扶关,因?yàn)檎w性能與記錄集大小及硬件平臺(tái)有關(guān)。根據(jù)測(cè)試結(jié)果建議當(dāng)一次性要取大量數(shù)據(jù)時(shí)這個(gè)值設(shè)置為100左右数冬,不要小于40节槐。注意搀庶,fetchsize不能設(shè)置太大,如果一次取出的數(shù)據(jù)大于JVM的內(nèi)存會(huì)導(dǎo)致內(nèi)存溢出铜异,所以建議不要超過1000哥倔,太大了也沒什么性能提高,反而可能會(huì)增加內(nèi)存溢出的危險(xiǎn)揍庄。
注:圖中fetchsize在128以后會(huì)有一些小的波動(dòng)咆蒿,這并不是測(cè)試誤差,而是由于resultset填充到具體對(duì)像時(shí)間不同的原因蚂子,由于resultset已經(jīng)到本地內(nèi)存里了沃测,所以估計(jì)是由于CPU的L1,L2 Cache命中率變化造成,由于變化不大食茎,所以筆者也未深入分析原因蒂破。
3.4、使用存儲(chǔ)過程
大型數(shù)據(jù)庫(kù)一般都支持存儲(chǔ)過程别渔,合理的利用存儲(chǔ)過程也可以提高系統(tǒng)性能附迷。如你有一個(gè)業(yè)務(wù)需要將A表的數(shù)據(jù)做一些加工然后更新到B表中,但是又不可能一條SQL完成哎媚,這時(shí)你需要如下3步操作:
a:將A表數(shù)據(jù)全部取出到客戶端喇伯;
b:計(jì)算出要更新的數(shù)據(jù);
c:將計(jì)算結(jié)果更新到B表拨与。
如果采用存儲(chǔ)過程你可以將整個(gè)業(yè)務(wù)邏輯封裝在存儲(chǔ)過程里稻据,然后在客戶端直接調(diào)用存儲(chǔ)過程處理,這樣可以減少網(wǎng)絡(luò)交互的成本截珍。
當(dāng)然攀甚,存儲(chǔ)過程也并不是十全十美箩朴,存儲(chǔ)過程有以下缺點(diǎn):
a岗喉、不可移植性,每種數(shù)據(jù)庫(kù)的內(nèi)部編程語法都不太相同炸庞,當(dāng)你的系統(tǒng)需要兼容多種數(shù)據(jù)庫(kù)時(shí)最好不要用存儲(chǔ)過程钱床。
b、學(xué)習(xí)成本高埠居,DBA一般都擅長(zhǎng)寫存儲(chǔ)過程查牌,但并不是每個(gè)程序員都能寫好存儲(chǔ)過程,除非你的團(tuán)隊(duì)有較多的開發(fā)人員熟悉寫存儲(chǔ)過程滥壕,否則后期系統(tǒng)維護(hù)會(huì)產(chǎn)生問題纸颜。
c、業(yè)務(wù)邏輯多處存在绎橘,采用存儲(chǔ)過程后也就意味著你的系統(tǒng)有一些業(yè)務(wù)邏輯不是在應(yīng)用程序里處理胁孙,這種架構(gòu)會(huì)增加一些系統(tǒng)維護(hù)和調(diào)試成本唠倦。
d、存儲(chǔ)過程和常用應(yīng)用程序語言不一樣涮较,它支持的函數(shù)及語法有可能不能滿足需求稠鼻,有些邏輯就只能通過應(yīng)用程序處理。
e狂票、如果存儲(chǔ)過程中有復(fù)雜運(yùn)算的話候齿,會(huì)增加一些數(shù)據(jù)庫(kù)服務(wù)端的處理成本,對(duì)于集中式數(shù)據(jù)庫(kù)可能會(huì)導(dǎo)致系統(tǒng)可擴(kuò)展性問題闺属。
f慌盯、為了提高性能,數(shù)據(jù)庫(kù)會(huì)把存儲(chǔ)過程代碼編譯成中間運(yùn)行代碼(類似于java的class文件)掂器,所以更像靜態(tài)語言润匙。當(dāng)存儲(chǔ)過程引用的對(duì)像(表、視圖等等)結(jié)構(gòu)改變后唉匾,存儲(chǔ)過程需要重新編譯才能生效孕讳,在24*7高并發(fā)應(yīng)用場(chǎng)景,一般都是在線變更結(jié)構(gòu)的巍膘,所以在變更的瞬間要同時(shí)編譯存儲(chǔ)過程厂财,這可能會(huì)導(dǎo)致數(shù)據(jù)庫(kù)瞬間壓力上升引起故障(Oracle數(shù)據(jù)庫(kù)就存在這樣的問題)。
個(gè)人觀點(diǎn):普通業(yè)務(wù)邏輯盡量不要使用存儲(chǔ)過程峡懈,定時(shí)性的ETL任務(wù)或報(bào)表統(tǒng)計(jì)函數(shù)可以根據(jù)團(tuán)隊(duì)資源情況采用存儲(chǔ)過程處理璃饱。
3.5、優(yōu)化業(yè)務(wù)邏輯
要通過優(yōu)化業(yè)務(wù)邏輯來提高性能是比較困難的肪康,這需要程序員對(duì)所訪問的數(shù)據(jù)及業(yè)務(wù)流程非常清楚荚恶。
舉一個(gè)案例:
某移動(dòng)公司推出優(yōu)惠套參,活動(dòng)對(duì)像為VIP會(huì)員并且2010年1磷支,2谒撼,3月平均話費(fèi)20元以上的客戶。
那我們的檢測(cè)邏輯為:
select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';
select vip_flag from member where phone_no='13988888888';
if avg_money>20 and vip_flag=true then
begin
??執(zhí)行套參();
end;
如果我們修改業(yè)務(wù)邏輯為:
select avg(money) as??avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';
if avg_money>20 then
begin
??select vip_flag from member where phone_no='13988888888';
??if vip_flag=true then
??begin
????執(zhí)行套參();
??end;
end;
通過這樣可以減少一些判斷vip_flag的開銷雾狈,平均話費(fèi)20元以下的用戶就不需要再檢測(cè)是否VIP了廓潜。
如果程序員分析業(yè)務(wù),VIP會(huì)員比例為1%善榛,平均話費(fèi)20元以上的用戶比例為90%辩蛋,那我們改成如下:
select vip_flag from member where phone_no='13988888888';
if vip_flag=true then
begin
??select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';
??if avg_money>20 then
??begin
????執(zhí)行套參();
??end;
end;
這樣就只有1%的VIP會(huì)員才會(huì)做檢測(cè)平均話費(fèi),最終大大減少了SQL的交互次數(shù)移盆。
以上只是一個(gè)簡(jiǎn)單的示例悼院,實(shí)際的業(yè)務(wù)總是比這復(fù)雜得多,所以一般只是高級(jí)程序員更容易做出優(yōu)化的邏輯咒循,但是我們需要有這樣一種成本優(yōu)化的意識(shí)据途。
3.6钮呀、使用ResultSet游標(biāo)處理記錄
現(xiàn)在大部分Java框架都是通過jdbc從數(shù)據(jù)庫(kù)取出數(shù)據(jù),然后裝載到一個(gè)list里再處理昨凡,list里可能是業(yè)務(wù)Object爽醋,也可能是hashmap。
由于JVM內(nèi)存一般都小于4G便脊,所以不可能一次通過sql把大量數(shù)據(jù)裝載到list里蚂四。為了完成功能,很多程序員喜歡采用分頁的方法處理哪痰,如一次從數(shù)據(jù)庫(kù)取1000條記錄遂赠,通過多次循環(huán)搞定,保證不會(huì)引起JVM Out of memory問題晌杰。
以下是實(shí)現(xiàn)此功能的代碼示例跷睦,t_employee表有10萬條記錄,設(shè)置分頁大小為1000:
d1 = Calendar.getInstance().getTime();
vsql = "select count(*) cnt from t_employee";
pstmt = conn.prepareStatement(vsql);
ResultSet rs = pstmt.executeQuery();
Integer cnt = 0;
while (rs.next()) {
?????????cnt = rs.getInt("cnt");
}
Integer lastid=0;
Integer pagesize=1000;
System.out.println("cnt:" + cnt);
String vsql = "select count(*) cnt from t_employee";
PreparedStatement pstmt = conn.prepareStatement(vsql);
ResultSet rs = pstmt.executeQuery();
Integer cnt = 0;
while (rs.next()) {
?????????cnt = rs.getInt("cnt");
}
Integer lastid = 0;
Integer pagesize = 1000;
System.out.println("cnt:" + cnt);
for (int i = 0; i <= cnt / pagesize; i++) {
?????????vsql = "select * from (select * from t_employee where id>? order by id) where rownum<=?";
?????????pstmt = conn.prepareStatement(vsql);
?????????pstmt.setFetchSize(1000);
?????????pstmt.setInt(1, lastid);
?????????pstmt.setInt(2, pagesize);
?????????rs = pstmt.executeQuery();
?????????int col_cnt = rs.getMetaData().getColumnCount();
?????????Object o;
?????????while (rs.next()) {
???????????????????for (int j = 1; j <= col_cnt; j++) {
????????????????????????????o = rs.getObject(j);
???????????????????}
???????????????????lastid = rs.getInt("id");
?????????}
?????????rs.close();
?????????pstmt.close();
}
以上代碼實(shí)際執(zhí)行時(shí)間為6.516秒
很多持久層框架為了盡量讓程序員使用方便肋演,封裝了jdbc通過statement執(zhí)行數(shù)據(jù)返回到resultset的細(xì)節(jié)抑诸,導(dǎo)致程序員會(huì)想采用分頁的方式處理問題。實(shí)際上如果我們采用jdbc原始的resultset游標(biāo)處理記錄爹殊,在resultset循環(huán)讀取的過程中處理記錄蜕乡,這樣就可以一次從數(shù)據(jù)庫(kù)取出所有記錄。顯著提高性能梗夸。
這里需要注意的是层玲,采用resultset游標(biāo)處理記錄時(shí),應(yīng)該將游標(biāo)的打開方式設(shè)置為FORWARD_READONLY模式(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY)反症,否則會(huì)把結(jié)果緩存在JVM里辛块,造成JVM Out of memory問題。
代碼示例:
String vsql ="select * from t_employee";
PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(100);
ResultSet rs = pstmt.executeQuery(vsql);
int col_cnt = rs.getMetaData().getColumnCount();
Object o;
while (rs.next()) {
?????????for (int j = 1; j <= col_cnt; j++) {
???????????????????o = rs.getObject(j);
?????????}
}
調(diào)整后的代碼實(shí)際執(zhí)行時(shí)間為3.156秒
從測(cè)試結(jié)果可以看出性能提高了1倍多铅碍,如果采用分頁模式數(shù)據(jù)庫(kù)每次還需發(fā)生磁盤IO的話那性能可以提高更多润绵。
iBatis等持久層框架考慮到會(huì)有這種需求,所以也有相應(yīng)的解決方案该酗,在iBatis里我們不能采用queryForList的方法授药,而應(yīng)用該采用queryWithRowHandler加回調(diào)事件的方式處理士嚎,如下所示:
MyRowHandler myrh=new?MyRowHandler();
sqlmap.queryWithRowHandler("getAllEmployee", myrh);
class?MyRowHandler?implements?RowHandler {
????publicvoid?handleRow(Object o) {
???????//todo something
????}
}
iBatis的queryWithRowHandler很好的封裝了resultset遍歷的事件處理呜魄,效果及性能與resultset遍歷一樣,也不會(huì)產(chǎn)生JVM內(nèi)存溢出莱衩。
4爵嗅、減少數(shù)據(jù)庫(kù)服務(wù)器CPU運(yùn)算
4.1、使用綁定變量
綁定變量是指SQL中對(duì)變化的值采用變量參數(shù)的形式提交笨蚁,而不是在SQL中直接拼寫對(duì)應(yīng)的值睹晒。
非綁定變量寫法:Select * from employee where id=1234567
綁定變量寫法:
Select * from employee where id=?
Preparestatement.setInt(1,1234567)
Java中Preparestatement就是為處理綁定變量提供的對(duì)像趟庄,綁定變量有以下優(yōu)點(diǎn):
1、防止SQL注入
2伪很、提高SQL可讀性
3戚啥、提高SQL解析性能,不使用綁定變更我們一般稱為硬解析锉试,使用綁定變量我們稱為軟解析猫十。
第1和第2點(diǎn)很好理解,做編碼的人應(yīng)該都清楚呆盖,這里不詳細(xì)說明拖云。關(guān)于第3點(diǎn),到底能提高多少性能呢应又,下面舉一個(gè)例子說明:
假設(shè)有這個(gè)這樣的一個(gè)數(shù)據(jù)庫(kù)主機(jī):
2個(gè)4核CPU?
100塊磁盤宙项,每個(gè)磁盤支持IOPS為160
業(yè)務(wù)應(yīng)用的SQL如下:
select * from table where pk=?
這個(gè)SQL平均4個(gè)IO(3個(gè)索引IO+1個(gè)數(shù)據(jù)IO)
IO緩存命中率75%(索引全在內(nèi)存中,數(shù)據(jù)需要訪問磁盤)
SQL硬解析CPU消耗:1ms??(常用經(jīng)驗(yàn)值)
SQL軟解析CPU消耗:0.02ms(常用經(jīng)驗(yàn)值)
假設(shè)CPU每核性能是線性增長(zhǎng)株扛,訪問內(nèi)存Cache中的IO時(shí)間忽略尤筐,要求計(jì)算系統(tǒng)對(duì)如上應(yīng)用采用硬解析與采用軟解析支持的每秒最大并發(fā)數(shù):
是否使用綁定變量CPU支持最大并發(fā)數(shù)磁盤IO支持最大并發(fā)數(shù)
不使用2*4*1000=8000100*160=16000
使用2*4*1000/0.02=400000100*160=16000
從以上計(jì)算可以看出,不使用綁定變量的系統(tǒng)當(dāng)并發(fā)達(dá)到8000時(shí)會(huì)在CPU上產(chǎn)生瓶頸洞就,當(dāng)使用綁定變量的系統(tǒng)當(dāng)并行達(dá)到16000時(shí)會(huì)在磁盤IO上產(chǎn)生瓶頸叔磷。所以如果你的系統(tǒng)CPU有瓶頸時(shí)請(qǐng)先檢查是否存在大量的硬解析操作。
使用綁定變量為何會(huì)提高SQL解析性能奖磁,這個(gè)需要從數(shù)據(jù)庫(kù)SQL執(zhí)行原理說明改基,一條SQL在Oracle數(shù)據(jù)庫(kù)中的執(zhí)行過程如下圖所示:
當(dāng)一條SQL發(fā)送給數(shù)據(jù)庫(kù)服務(wù)器后,系統(tǒng)首先會(huì)將SQL字符串進(jìn)行hash運(yùn)算咖为,得到hash值后再?gòu)姆?wù)器內(nèi)存里的SQL緩存區(qū)中進(jìn)行檢索秕狰,如果有相同的SQL字符,并且確認(rèn)是同一邏輯的SQL語句躁染,則從共享池緩存中取出SQL對(duì)應(yīng)的執(zhí)行計(jì)劃鸣哀,根據(jù)執(zhí)行計(jì)劃讀取數(shù)據(jù)并返回結(jié)果給客戶端。
如果在共享池中未發(fā)現(xiàn)相同的SQL則根據(jù)SQL邏輯生成一條新的執(zhí)行計(jì)劃并保存在SQL緩存區(qū)中吞彤,然后根據(jù)執(zhí)行計(jì)劃讀取數(shù)據(jù)并返回結(jié)果給客戶端我衬。
為了更快的檢索SQL是否在緩存區(qū)中,首先進(jìn)行的是SQL字符串hash值對(duì)比饰恕,如果未找到則認(rèn)為沒有緩存挠羔,如果存在再進(jìn)行下一步的準(zhǔn)確對(duì)比,所以要命中SQL緩存區(qū)應(yīng)保證SQL字符是完全一致埋嵌,中間有大小寫或空格都會(huì)認(rèn)為是不同的SQL破加。
如果我們不采用綁定變量,采用字符串拼接的模式生成SQL,那么每條SQL都會(huì)產(chǎn)生執(zhí)行計(jì)劃雹嗦,這樣會(huì)導(dǎo)致共享池耗盡范舀,緩存命中率也很低合是。
一些不使用綁定變量的場(chǎng)景:
a、數(shù)據(jù)倉(cāng)庫(kù)應(yīng)用锭环,這種應(yīng)用一般并發(fā)不高聪全,但是每個(gè)SQL執(zhí)行時(shí)間很長(zhǎng),SQL解析的時(shí)間相比SQL執(zhí)行時(shí)間比較小辅辩,綁定變量對(duì)性能提高不明顯荔烧。數(shù)據(jù)倉(cāng)庫(kù)一般都是內(nèi)部分析應(yīng)用,所以也不太會(huì)發(fā)生SQL注入的安全問題汽久。
b鹤竭、數(shù)據(jù)分布不均勻的特殊邏輯,如產(chǎn)品表景醇,記錄有1億臀稚,有一產(chǎn)品狀態(tài)字段,上面建有索引三痰,有審核中吧寺,審核通過,審核未通過3種狀態(tài)散劫,其中審核通過9500萬稚机,審核中1萬,審核不通過499萬获搏。
要做這樣一個(gè)查詢:
select count(*) from product where status=?
采用綁定變量的話赖条,那么只會(huì)有一個(gè)執(zhí)行計(jì)劃,如果走索引訪問常熙,那么對(duì)于審核中查詢很快纬乍,對(duì)審核通過和審核不通過會(huì)很慢;如果不走索引裸卫,那么對(duì)于審核中與審核通過和審核不通過時(shí)間基本一樣仿贬;
對(duì)于這種情況應(yīng)該不使用綁定變量,而直接采用字符拼接的方式生成SQL墓贿,這樣可以為每個(gè)SQL生成不同的執(zhí)行計(jì)劃茧泪,如下所示。
select count(*) from product where status='approved'; //不使用索引
select count(*) from product where status='tbd'; //不使用索引
select count(*) from product where status='auditing';//使用索引
4.2聋袋、合理使用排序
Oracle的排序算法一直在優(yōu)化队伟,但是總體時(shí)間復(fù)雜度約等于nLog(n)。普通OLTP系統(tǒng)排序操作一般都是在內(nèi)存里進(jìn)行的舱馅,對(duì)于數(shù)據(jù)庫(kù)來說是一種CPU的消耗缰泡,曾在PC機(jī)做過測(cè)試,單核普通CPU在1秒鐘可以完成100萬條記錄的全內(nèi)存排序操作代嗤,所以說由于現(xiàn)在CPU的性能增強(qiáng)棘钞,對(duì)于普通的幾十條或上百條記錄排序?qū)ο到y(tǒng)的影響也不會(huì)很大。但是當(dāng)你的記錄集增加到上萬條以上時(shí)干毅,你需要注意是否一定要這么做了宜猜,大記錄集排序不僅增加了CPU開銷,而且可能會(huì)由于內(nèi)存不足發(fā)生硬盤排序的現(xiàn)象硝逢,當(dāng)發(fā)生硬盤排序時(shí)性能會(huì)急劇下降姨拥,這種需求需要與DBA溝通再?zèng)Q定,取決于你的需求和數(shù)據(jù)渠鸽,所以只有你自己最清楚叫乌,而不要被別人說排序很慢就嚇倒。
以下列出了可能會(huì)發(fā)生排序操作的SQL語法:
Order by
Group by
Distinct
Exists子查詢
Not Exists子查詢
In子查詢
Not In子查詢
Union(并集)徽缚,Union All也是一種并集操作憨奸,但是不會(huì)發(fā)生排序,如果你確認(rèn)兩個(gè)數(shù)據(jù)集不需要執(zhí)行去除重復(fù)數(shù)據(jù)操作凿试,那請(qǐng)使用Union All?代替Union排宰。
Minus(差集)
Intersect(交集)
Create Index
Merge Join,這是一種兩個(gè)表連接的內(nèi)部算法那婉,執(zhí)行時(shí)會(huì)把兩個(gè)表先排序好再連接板甘,應(yīng)用于兩個(gè)大表連接的操作。如果你的兩個(gè)表連接的條件都是等值運(yùn)算详炬,那可以采用Hash Join來提高性能盐类,因?yàn)镠ash Join使用Hash?運(yùn)算來代替排序的操作。具體原理及設(shè)置參考SQL執(zhí)行計(jì)劃優(yōu)化專題呛谜。
4.3傲醉、減少比較操作
我們SQL的業(yè)務(wù)邏輯經(jīng)常會(huì)包含一些比較操作,如a=b呻率,a
Like模糊查詢硬毕,如下所示:
a like ‘%abc%’
Like模糊查詢對(duì)于數(shù)據(jù)庫(kù)來說不是很擅長(zhǎng),特別是你需要模糊檢查的記錄有上萬條以上時(shí)礼仗,性能比較糟糕吐咳,這種情況一般可以采用專用Search或者采用全文索引方案來提高性能。
不能使用索引定位的大量In List元践,如下所示:
a in (:1,:2,:3,…,:n)???----n>20
如果這里的a字段不能通過索引比較韭脊,那數(shù)據(jù)庫(kù)會(huì)將字段與in里面的每個(gè)值都進(jìn)行比較運(yùn)算,如果記錄數(shù)有上萬以上单旁,會(huì)明顯感覺到SQL的CPU開銷加大沪羔,這個(gè)情況有兩種解決方式:
a、??將in列表里面的數(shù)據(jù)放入一張中間小表,采用兩個(gè)表Hash Join關(guān)聯(lián)的方式處理蔫饰;
b切蟋、??采用str2varList方法將字段串列表轉(zhuǎn)換一個(gè)臨時(shí)表處理鹃唯,關(guān)于str2varList方法可以在網(wǎng)上直接查詢费彼,這里不詳細(xì)介紹削咆。
以上兩種解決方案都需要與中間表Hash Join的方式才能提高性能,如果采用了Nested Loop的連接方式性能會(huì)更差杖剪。
如果發(fā)現(xiàn)我們的系統(tǒng)IO沒問題但是CPU負(fù)載很高冻押,就有可能是上面的原因,這種情況不太常見盛嘿,如果遇到了最好能和DBA溝通并確認(rèn)準(zhǔn)確的原因洛巢。
4.4、大量復(fù)雜運(yùn)算在客戶端處理
什么是復(fù)雜運(yùn)算次兆,一般我認(rèn)為是一秒鐘CPU只能做10萬次以內(nèi)的運(yùn)算稿茉。如含小數(shù)的對(duì)數(shù)及指數(shù)運(yùn)算、三角函數(shù)类垦、3DES及BASE64數(shù)據(jù)加密算法等等狈邑。
如果有大量這類函數(shù)運(yùn)算,盡量放在客戶端處理蚤认,一般CPU每秒中也只能處理1萬-10萬次這樣的函數(shù)運(yùn)算米苹,放在數(shù)據(jù)庫(kù)內(nèi)不利于高并發(fā)處理。
5砰琢、利用更多的資源
5.1蘸嘶、客戶端多進(jìn)程并行訪問
多進(jìn)程并行訪問是指在客戶端創(chuàng)建多個(gè)進(jìn)程(線程),每個(gè)進(jìn)程建立一個(gè)與數(shù)據(jù)庫(kù)的連接陪汽,然后同時(shí)向數(shù)據(jù)庫(kù)提交訪問請(qǐng)求训唱。當(dāng)數(shù)據(jù)庫(kù)主機(jī)資源有空閑時(shí),我們可以采用客戶端多進(jìn)程并行訪問的方法來提高性能挚冤。如果數(shù)據(jù)庫(kù)主機(jī)已經(jīng)很忙時(shí)况增,采用多進(jìn)程并行訪問性能不會(huì)提高,反而可能會(huì)更慢训挡。所以使用這種方式最好與DBA或系統(tǒng)管理員進(jìn)行溝通后再?zèng)Q定是否采用澳骤。
例如:
我們有10000個(gè)產(chǎn)品ID,現(xiàn)在需要根據(jù)ID取出產(chǎn)品的詳細(xì)信息澜薄,如果單線程訪問为肮,按每個(gè)IO要5ms計(jì)算,忽略主機(jī)CPU運(yùn)算及網(wǎng)絡(luò)傳輸時(shí)間肤京,我們需要50s才能完成任務(wù)颊艳。如果采用5個(gè)并行訪問,每個(gè)進(jìn)程訪問2000個(gè)ID,那么10s就有可能完成任務(wù)棋枕。
那是不是并行數(shù)越多越好呢白修,開1000個(gè)并行是否只要50ms就搞定,答案肯定是否定的戒悠,當(dāng)并行數(shù)超過服務(wù)器主機(jī)資源的上限時(shí)性能就不會(huì)再提高熬荆,如果再增加反而會(huì)增加主機(jī)的進(jìn)程間調(diào)度成本和進(jìn)程沖突機(jī)率舟山。
以下是一些如何設(shè)置并行數(shù)的基本建議:
如果瓶頸在服務(wù)器主機(jī)绸狐,但是主機(jī)還有空閑資源,那么最大并行數(shù)取主機(jī)CPU核數(shù)和主機(jī)提供數(shù)據(jù)服務(wù)的磁盤數(shù)兩個(gè)參數(shù)中的最小值累盗,同時(shí)要保證主機(jī)有資源做其它任務(wù)寒矿。
如果瓶頸在客戶端處理,但是客戶端還有空閑資源若债,那建議不要增加SQL的并行符相,而是用一個(gè)進(jìn)程取回?cái)?shù)據(jù)后在客戶端起多個(gè)進(jìn)程處理即可,進(jìn)程數(shù)根據(jù)客戶端CPU核數(shù)計(jì)算蠢琳。
如果瓶頸在客戶端網(wǎng)絡(luò)啊终,那建議做數(shù)據(jù)壓縮或者增加多個(gè)客戶端,采用map reduce的架構(gòu)處理傲须。
如果瓶頸在服務(wù)器網(wǎng)絡(luò)蓝牲,那需要增加服務(wù)器的網(wǎng)絡(luò)帶寬或者在服務(wù)端將數(shù)據(jù)壓縮后再處理了。
5.2泰讽、數(shù)據(jù)庫(kù)并行處理
數(shù)據(jù)庫(kù)并行處理是指客戶端一條SQL的請(qǐng)求例衍,數(shù)據(jù)庫(kù)內(nèi)部自動(dòng)分解成多個(gè)進(jìn)程并行處理,如下圖所示:
并不是所有的SQL都可以使用并行處理已卸,一般只有對(duì)表或索引進(jìn)行全部訪問時(shí)才可以使用并行佛玄。數(shù)據(jù)庫(kù)表默認(rèn)是不打開并行訪問,所以需要指定SQL并行的提示累澡,如下所示:
select/*+parallel(a,4)*/?* from employee;
并行的優(yōu)點(diǎn):
使用多進(jìn)程處理梦抢,充分利用數(shù)據(jù)庫(kù)主機(jī)資源(CPU,IO),提高性能愧哟。
并行的缺點(diǎn):
1奥吩、單個(gè)會(huì)話占用大量資源,影響其它會(huì)話翅雏,所以只適合在主機(jī)負(fù)載低時(shí)期使用圈驼;
2、只能采用直接IO訪問望几,不能利用緩存數(shù)據(jù)绩脆,所以執(zhí)行前會(huì)觸發(fā)將臟緩存數(shù)據(jù)寫入磁盤操作。
注:
1、并行處理在OLTP類系統(tǒng)中慎用靴迫,使用不當(dāng)會(huì)導(dǎo)致一個(gè)會(huì)話把主機(jī)資源全部占用惕味,而正常事務(wù)得不到及時(shí)響應(yīng),所以一般只是用于數(shù)據(jù)倉(cāng)庫(kù)平臺(tái)玉锌。
2名挥、一般對(duì)于百萬級(jí)記錄以下的小表采用并行訪問性能并不能提高,反而可能會(huì)讓性能更差主守。