SQL索引優化

序言
數據庫的優化方法有不少種,在應用層來講,主要是基於索引的優化。本次祕笈根據實際的工做經驗,在研發原來已有的方法的基礎上,進行了一些擴充,總結了基於索引的SQL語句優化的降龍十八掌,但願有一天你能用其中一掌來馴服客服業務中橫行的‘惡龍’
總綱
創建必要的索引
此次傳授的降龍十八掌,總綱只有一句話:創建必要的索引,這就是後面降龍十八掌的內功基礎。這一點看似容易實際卻很難。難就難在如何判斷哪些索引是必要的,哪些又是沒必要要的。判斷的最終標準是看這些索引是否對咱們的數據庫性能有所幫助。具體到方法上,就必須熟悉數據庫應用程序中的全部SQL語句,從中統計出經常使用的可能對性能有影響的部分SQL,分析、概括出做爲Where條件子句的字段及其組合方式;在這一基礎上能夠初步判斷出哪些表的哪些字段應該創建索引。其次,必須熟悉應用程序。必須瞭解哪些表是數據操做頻繁的表;哪些表常常與其餘表進行鏈接;哪些表中的數據量可能很大;對於數據量大的表,其中各個字段的數據分佈狀況如何;等等。對於知足以上條件的這些表,必須重點關注,由於在這些表上的索引,將對SQL語句的性能產生舉足輕重的影響。不過下面仍是總結了一降低龍十八掌內功的入門基礎,創建索引經常使用的規則以下:前端

 

一、表的主鍵、外鍵必須有索引;數據庫

二、數據量超過300的表應該有索引;session

三、常常與其餘表進行鏈接的表,在鏈接字段上應該創建索引;數據庫設計

四、常常出如今Where子句中的字段,特別是大表的字段,應該創建索引;函數

五、索引應該建在選擇性高的字段上;性能

六、索引應該建在小字段上,對於大的文本字段甚至超長字段,不要建索引;優化

七、複合索引的創建須要進行仔細分析;儘可能考慮用單字段索引代替:設計

A、正確選擇複合索引中的主列字段,通常是選擇性較好的字段;orm

B、複合索引的幾個字段是否常常同時以AND方式出如今Where子句中?單字段查詢是否極少甚至沒有?若是是,則能夠創建複合索引;不然考慮單字段索引;排序

C、若是複合索引中包含的字段常常單獨出如今Where子句中,則分解爲多個單字段索引;

D、若是複合索引所包含的字段超過3個,那麼仔細考慮其必要性,考慮減小複合的字段;

E、若是既有單字段索引,又有這幾個字段上的複合索引,通常能夠刪除複合索引;

八、頻繁進行數據操做的表,不要創建太多的索引;

九、刪除無用的索引,避免對執行計劃形成負面影響;

以上是一些廣泛的創建索引時的判斷依據。一言以蔽之,索引的創建必須慎重,對每一個索引的必要性都應該通過仔細分析,要有創建的依據。由於太多的索引與不充分、不正確的索引對性能都毫無益處:在表上創建的每一個索引都會增長存儲開銷,索引對於插入、刪除、更新操做也會增長處理上的開銷。另外,過多的複合索引,在有單字段索引的狀況下,通常都是沒有存在價值的;相反,還會下降數據增長刪除時的性能,特別是對頻繁更新的表來講,負面影響更大。

第一掌 避免對列的操做


任何對列的操做均可能致使全表掃描,這裏所謂的操做包括數據庫函數、計算表達式等等,查詢時要儘量將操做移至等式的右邊,甚至去掉函數。


例1:下列SQL條件語句中的列都建有恰當的索引,但30萬行數據狀況下執行速度卻很是慢:


select * from record where substrb(CardNo,1,4)='5378'(13秒)


select * from record where amount/30< 1000(11秒)


select * from record where to_char(ActionTime,'yyyymmdd')='19991201'(10秒)


因爲where子句中對列的任何操做結果都是在SQL運行時逐行計算獲得的,所以它不得不進行表掃描,而沒有使用該列上面的索引;若是這些結果在查詢編譯時就能獲得,那麼就能夠被SQL優化器優化,使用索引,避免表掃描,所以將SQL重寫以下:


select * from record where CardNo like '5378%'(< 1秒)


select * from record where amount < 1000*30(< 1秒)


select * from record where ActionTime= to_date ('19991201' ,'yyyymmdd')(< 1秒)


差異是很明顯的!


第二掌 避免沒必要要的類型轉換


須要注意的是,儘可能避免潛在的數據類型轉換。如將字符型數據與數值型數據比較,ORACLE會自動將字符型用to_number()函數進行轉換,從而致使全表掃描。


例2:表tab1中的列col1是字符型(char),則如下語句存在類型轉換:


select col1,col2 from tab1 where col1>10,


應該寫爲: select col1,col2 from tab1 where col1>'10'。


第三掌 增長查詢的範圍限制


增長查詢的範圍限制,避免全範圍的搜索。


例3:如下查詢表record 中時間ActionTime小於2001年3月1日的數據:


select * from record where ActionTime < to_date ('20010301' ,'yyyymm')


查詢計劃代表,上面的查詢對錶進行全表掃描,若是咱們知道表中的最先的數據爲2001年1月1日,那麼,能夠增長一個最小時間,使查詢在一個完整的範圍以內。修改以下: select * from record where


ActionTime < to_date ('20010301' ,'yyyymm')


and ActionTime > to_date ('20010101' ,'yyyymm')


後一種SQL語句將利用上ActionTime字段上的索引,從而提升查詢效率。把'20010301'換成一個變量,根據取值的機率,能夠有一半以上的 機會提升效率。同理,對於大於某個值的查詢,若是知道當前可能的最大值,也能夠在Where子句中加上 「AND 列名<MAX(最大值)」。


第四掌 儘可能去掉"IN"、"OR"


含有"IN"、"OR"的Where子句常會使用工做表,使索引失效;若是不產生大量重複值,能夠考慮把子句拆開;拆開的子句中應該包含索引。


例4:select count(*) from stuff where id_no in('0','1')(23秒)


能夠考慮將or子句分開:


select count(*) from stuff where id_no='0'


select count(*) from stuff where id_no='1'


而後再作一個簡單的加法,與原來的SQL語句相比,查詢速度更快。

第五掌 儘可能去掉 "<>"

儘可能去掉 "<>",避免全表掃描,若是數據是枚舉值,且取值範圍固定,則修改成"OR"方式。

例5:

UPDATE SERVICEINFO SET STATE=0 WHERE STATE<>0;

以 上語句因爲其中包含了"<>",執行計劃中用了全表掃描(TABLE ACCESSFULL),沒有用到state字段上的索引。實際應用中,因爲業務邏輯的限制,字段state爲枚舉值,只能等於0,1或2,並且,值等於=1,2的不多,所以能夠去掉"<>",利用索引來提升效率。

修改成:UPDATE SERVICEINFO SET STATE=0 WHERE STATE = 1 OR STATE = 2 。進一步的修改能夠參考第4種方法。

第六掌 去掉Where子句中的IS NULL和IS NOT NULL

Where字句中的IS NULL和IS NOT NULL將不會使用索引而是進行全表搜索,所以須要經過改變查詢方式,分狀況討論等方法,去掉Where子句中的IS NULL和IS NOT NULL。

第七掌 索引提升數據分佈不均勻時查詢效率

索引的選擇性低,但數據的值分佈差別很大時,仍然能夠利用索引提升效率。A、數據分佈不均勻的特殊狀況下,選擇性不高的索引也要建立。

表 ServiceInfo中數據量很大,假設有一百萬行,其中有一個字段DisposalCourseFlag,取值範圍爲枚舉值:[0,1,2,3,4,5,6,7]。按照前面說的索引創建的規則,「選擇性不高的字段不該該創建索引,該字段只有8種取值,索引值的重複率很高,索引選擇性明顯很低,所以不建索引。然而,因爲該字段上數據值的分佈狀況很是特殊,具體以下表:


取值範圍 1~5 6 7

佔總數據量的百分比 1% 98% 1%


並且,經常使用的查詢中,查詢DisposalCourseFlag<6 的狀況既多又頻繁,毫無疑問,若是可以創建索引,而且被應用,那麼將大大提升這種狀況的查詢效率。所以,咱們須要在該字段上創建索引。

第八掌 利用HINT強制指定索引

在ORACLE優化器沒法用上合理索引的狀況下,利用HINT強制指定索引。

繼續上面7的例子,ORACLE缺省認定,表中列的值是在全部數據行中均勻分佈的,也就是說,在一百萬數據量下,每種DisposalCourseFlag值各有12.5萬數據行與之對應。假設SQL搜索條件DisposalCourseFlag=2,利用DisposalCourseFlag列上的索引進行數據搜索效率,每每不比全表掃描的高,ORACLE所以對索引「視而不見」,從而在查詢路徑的選擇中,用其餘字段上的索引甚至全表掃描。根據咱們上面的分析,數據值的分佈很特殊,嚴重的不均勻。爲了利用索引提升效率,此時,一方面能夠單獨對該字段或該表用analyze語句進行分析,對該列蒐集足夠的統計數據,使ORACLE在查詢選擇性較高的值時能用上索引;另外一方面,能夠利用HINT提示,在SELECT關鍵字後面,加上「/*+INDEX(表名稱,索引名稱)*/」的方式,強制ORACLE優化器用上該索引。

好比: select * from serviceinfo where DisposalCourseFlag=1 ;

上面的語句,實際執行中ORACLE用了全表掃描,加上藍色提示部分後,用到索引查詢。以下:

select /*+ INDEX(SERVICEINFO,IX_S_DISPOSALCOURSEFLAG) */ *

from serviceinfo where DisposalCourseFlag=1;

請注意,這種方法會加大代碼維護的難度,並且該字段上索引的名稱被改變以後,必需要同步全部指定索引的HINT代碼,不然HINT提示將被ORACLE忽略掉。

第九掌 屏蔽無用索引

繼續上面8的例子,因爲實際查詢中,還有涉及到DisposalCourseFlag=6的查詢,而此時若是用上該字段上的索引,將是很是不明智的,效率也極低。所以這種狀況下,咱們須要用特殊的方法屏蔽該索引,以便ORACLE選擇其餘字段上的索引。好比,若是字段爲數值型的就在表達式的字段名後,添加「+ 0」,爲字符型的就並上空串:「||""」

如: select * from serviceinfo where DisposalCourseFlag+ 0 = 6 and workNo = '36' 。

不過,不要把該用的索引屏蔽掉了,不然一樣會產生低效率的全表掃描。
第十掌 分解複雜查詢,用常量代替變量

對於複雜的Where條件組合,Where中含有多個帶索引的字段,考慮用IF語句分狀況進行討論;同時,去掉沒必要要的外來參數條件,減低複雜度,以便在不一樣狀況下用不一樣字段上的索引。

繼續上面9的例子,對於包含

Where (DisposalCourseFlag < v_DisPosalCourseFlag) or(v_DisPosalCourseFlag is null) and....的查詢,(這裏v_DisPosalCourseFlag爲一個輸入變量,取值範圍可能爲[NULL,0,1,2,3,4,5,6,7]),能夠考慮分狀況用IF語句進行討論,相似:

IF v_DisPosalCourseFlag =1 THEN

Where DisposalCourseFlag = 1 and ....

ELSIF v_DisPosalCourseFlag =2 THEN

Where DisposalCourseFlag = 2 and ....

。。。。。。

第十一掌 like子句儘可能前端匹配

由於like參數使用的很是頻繁,所以若是可以對like子句使用索引,將很高的提升查詢的效率。

例6:select * from city where name like ‘%S%’

以上查詢的執行計劃用了全表掃描(TABLE ACCESS FULL),若是可以修改成:

select * from city where name like ‘S%’

那 麼查詢的執行計劃將會變成(INDEX RANGE SCAN),成功的利用了name字段的索引。這意味着OracleSQL優化器會識別出用於索引的like子句,只要該查詢的匹配端是具體值。所以咱們在作like查詢時,應該儘可能使查詢的匹配端是具體值,即便用like ‘S%’。

第十二掌 用Case語句合併多重掃描

咱們經常必須基於多組數據表計算不一樣的彙集。例以下例經過三個獨立查詢:

例8:1)select count(*) from emp where sal<1000;

2)select count(*) from emp where sal between 1000 and 5000;

3)select count(*) from emp where sal>5000;

這樣咱們須要進行三次全表查詢,可是若是咱們使用case語句:

select

count (sale when sal <1000 then 1 else null end) count_poor,

count (sale when between 1000 and 5000 then 1 else null end) count_blue,

count (sale when sal >5000 then 1 else null end) count_poor

from emp;

這樣查詢的結果同樣,可是執行計劃只進行了一次全表查詢。

第十三掌 使用nls_date_format

例9:

select * from record where to_char(ActionTime,'mm')='12'

這個查詢的執行計劃將是全表查詢,若是咱們改變nls_date_format,

SQL>alert session set nls_date_formate=’MM’;

如今從新修改上面的查詢:

select * from record where ActionTime='12'

這樣就能使用actiontime上的索引了,它的執行計劃將是(INDEX RANGE SCAN)。
第十四掌 使用基於函數的索引

前面談到任何對列的操做均可能致使全表掃描,例如:

select * from emp where substr(ename,1,2)=’SM’;

可是這種查詢在客服系統又常用,咱們能夠建立一個帶有substr函數的基於函數的索引,

create index emp_ename_substr on eemp ( substr(ename,1,2) );

這樣在執行上面的查詢語句時,這個基於函數的索引將排上用場,執行計劃將是(INDEX RANGE SCAN)。

第十五掌 基於函數的索引要求等式匹配

上面的例子中,咱們建立了基於函數的索引,可是若是執行下面的查詢:

select * from emp where substr(ename,1,1)=’S’

得 到的執行計劃將仍是(TABLE ACCESSFULL),由於只有當數據列可以等式匹配時,基於函數的索引才能生效,這樣對於這種索引的計劃和維護的要求都很高。請注意,向表中添加索引是很是危險的操做,由於這將致使許多查詢執行計劃的變動。然而,若是咱們使用基於函數的索引就不會產生這樣的問題,由於Oracle只有在查詢使用了匹配的內置函數時纔會使用這種類型的索引。

第十六掌 使用分區索引

在用分析命令對分區索引進行分析時,每個分區的數據值的範圍信息會放入Oracle的數據字典中。Oracle能夠利用這個信息來提取出那些只與SQL查詢相關的數據分區。

例如,假設你已經定義了一個分區索引,而且某個SQL語句須要在一個索引分區中進行一次索引掃描。Oracle會僅僅訪問這個索引分區,並且會在這個分區上調用一個此索引範圍的快速全掃描。由於不須要訪問整個索引,因此提升了查詢的速度。

第十七掌 使用位圖索引

位圖索引能夠從本質上提升使用了小於1000個惟一數據值的數據列的查詢速度,由於在位圖索引中進行的檢索是在RAM中完成的,並且也老是比傳統的B樹索引的速度要快。對於那些少於1000個惟一數據值的數據列創建位圖索引,可使執行效率更快。

第十八掌 決定使用全表掃描仍是使用索引

和全部的祕笈同樣,最後一招都會又回到起點,最後咱們來討論一下是否須要創建索引,也許進行全表掃描更快。在大多數狀況下,全表掃描可能會致使更多的物理磁盤輸入輸出,可是全表掃描有時又可能會由於高度並行化的存在而執行的更快。若是查詢的表徹底沒有順序,那麼一個要返回記錄數小於10%的查詢可能會讀取表中大部分的數據塊,這樣使用索引會使查詢效率提升不少。可是若是表很是有順序,那麼若是查詢的記錄數大於40%時,可能使用全表掃描更快。所以,有一個索引範圍掃描的整體原則是:

1)對於原始排序的表 僅讀取少於表記錄數40%的查詢應該使用索引範圍掃描。反之,讀取記錄數目多於表記錄數的40%的查詢應該使用全表掃描。

2)對於未排序的表 僅讀取少於表記錄數7%的查詢應該使用索引範圍掃描。反之,讀取記錄數目多於表記錄數的7%的查詢應該使用全表掃描。

總結

以上的招式,是徹底能夠相互結合同時運用的。並且各類方法之間相互影響,緊密聯繫。這種聯繫既存在一致性,也可能帶來衝突,當衝突發生時,須要根據實際狀況進行選擇,沒有固定的模式。最後決定SQL優化功力的因素就是對ORACLE內功的掌握程度了。

另外,值得注意的是:隨着時間的推移和數據的累計與變化,ORACLE對SQL語句的執行計劃也會改變,好比:基於代價的優化方法,隨着數據量的增大,優化器可能錯誤的不選擇索引而採用全表掃描。這種狀況多是由於統計信息已通過時,在數據量變化很大後沒有及時分析表;但若是對錶進行分析以後,仍然沒有用上合理的索引,那麼就有必要對SQL語句用HINT提示,強制用合理的索引。但這種HINT提示也不能濫用,由於這種方法過於複雜,缺少通用性和應變能力,同時也增長了維護上的代價;相對來講,基於函數右移、去掉「IN ,OR ,<> ,IS NOT NULL」、分解複雜的SQL語句等等方法,倒是「放之四海皆準」的,能夠放心大膽的使用。

同時,優化也不是「一勞永逸」的,必須隨着狀況的改變進行相應的調整。當數據庫設計發生變化,包括更改表結構:字段和索引的增長、刪除或更名等;業務邏輯發生變化:如查詢方式、取值範圍發生改變等等。在這種狀況下,也必須對原有的優化進行調整,以適應效率上的需求。

相關文章
相關標籤/搜索