大家都在討論關于數據庫優化方面的東東,剛好參與開發了一個數據倉庫方面的項目,以下的一點東西算是數據庫優化方面的學習+實戰的一些心得體會了,拿出來大家共享。歡迎批評指正阿!?
SQL語句:?
是對數據庫(數據)進行操作的惟一途徑;?
消耗了70%~90%的數據庫資源;獨立于程序設計邏輯,相對于對程序源代碼的優化,對SQL語句的優化在時間成本和風險上的代價都很低;?
可以有不同的寫法;易學,難精通。?
SQL優化:?
固定的SQL書寫習慣,相同的查詢盡量保持相同,存儲過程的效率較高。?
應該編寫與其格式一致的語句,包括字母的大小寫、標點符號、換行的位置等都要一致?
ORACLE優化器:?
在任何可能的時候都會對表達式進行評估,并且把特定的語法結構轉換成等價的結構,這么做的原因是?
要么結果表達式能夠比源表達式具有更快的速度?
要么源表達式只是結果表達式的一個等價語義結構?
不同的SQL結構有時具有同樣的操作(例如:=?ANY?(subquery)?and?IN?(subquery)),ORACLE會把他們映射到一個單一的語義結構。?
1?常量優化:?
常量的計算是在語句被優化時一次性完成,而不是在每次執行時。下面是檢索月薪大于2000的的表達式:?
sal?>?24000/12?
sal?>?2000?
sal*12?>?24000?
如果SQL語句包括第一種情況,優化器會簡單地把它轉變成第二種。?
優化器不會簡化跨越比較符的表達式,例如第三條語句,鑒于此,應盡量寫用常量跟字段比較檢索的表達式,而不要將字段置于表達式當中。否則沒有辦法優化,比如如果sal上有索引,第一和第二就可以使用,第三就難以使用。?
2?操作符優化:?
優化器把使用LIKE操作符和一個沒有通配符的表達式組成的檢索表達式轉換為一個“=”操作符表達式。?
例如:優化器會把表達式ename?LIKE?'SMITH'轉換為ename?=?'SMITH'?
優化器只能轉換涉及到可變長數據類型的表達式,前一個例子中,如果ENAME字段的類型是CHAR(10),?那么優化器將不做任何轉換。?
一般來講LIKE比較難以優化。?
其中:?
~~?IN?操作符優化:?
優化器把使用IN比較符的檢索表達式替換為等價的使用“=”和“OR”操作符的檢索表達式。?
例如,優化器會把表達式ename?IN?('SMITH','KING','JONES')替換為?
ename?=?'SMITH'?OR?ename?=?'KING'?OR?ename?=?'JONES‘?
~~?ANY和SOME?操作符優化:?
優化器將跟隨值列表的ANY和SOME檢索條件用等價的同等操作符和“OR”組成的表達式替換。?
例如,優化器將如下所示的第一條語句用第二條語句替換:?
sal?>?ANY?(:first_sal,?:second_sal)?
sal?>?:first_sal?OR?sal?>?:second_sal?
優化器將跟隨子查詢的ANY和SOME檢索條件轉換成由“EXISTS”和一個相應的子查詢組成的檢索表達式。?
例如,優化器將如下所示的第一條語句用第二條語句替換:?
x?>?ANY?(SELECT?sal?FROM?emp?WHERE?job?=?'ANALYST')?
EXISTS?(SELECT?sal?FROM?emp?WHERE?job?=?'ANALYST'?AND?x?>?sal)?
~~?ALL操作符優化:?
優化器將跟隨值列表的ALL操作符用等價的“=”和“AND”組成的表達式替換。例如:?
sal?>?ALL?(:first_sal,?:second_sal)表達式會被替換為:?
sal?>?:first_sal?AND?sal?>?:second_sal?
對于跟隨子查詢的ALL表達式,優化器用ANY和另外一個合適的比較符組成的表達式替換。例如?
x?>?ALL?(SELECT?sal?FROM?emp?WHERE?deptno?=?10)?替換為:?
NOT?(x?<=?ANY?(SELECT?sal?FROM?emp?WHERE?deptno?=?10))?
接下來優化器會把第二個表達式適用ANY表達式的轉換規則轉換為下面的表達式:?
NOT?EXISTS?(SELECT?sal?FROM?emp?WHERE?deptno?=?10?AND?x?<=?sal)?
~~?BETWEEN?操作符優化:?
優化器總是用“>=”和“<=”比較符來等價的代替BETWEEN操作符。?
例如:優化器會把表達式sal?BETWEEN?2000?AND?3000用sal?>=?2000?AND?sal?<=?3000來代替。?
~~?NOT?操作符優化:?
優化器總是試圖簡化檢索條件以消除“NOT”邏輯操作符的影響,這將涉及到“NOT”操作符的消除以及代以相應的比較運算符。?
例如,優化器將下面的第一條語句用第二條語句代替:?
NOT?deptno?=?(SELECT?deptno?FROM?emp?WHERE?ename?=?'TAYLOR')?
deptno?<>?(SELECT?deptno?FROM?emp?WHERE?ename?=?'TAYLOR')?
通常情況下一個含有NOT操作符的語句有很多不同的寫法,優化器的轉換原則是使“NOT”操作符后邊的子句盡可能的簡單,即使可能會使結果表達式包含了更多的“NOT”操作符。?
例如,優化器將如下所示的第一條語句用第二條語句代替:?
NOT?(sal?<?1000?OR?comm?IS?NULL)?
NOT?sal?<?1000?AND?comm?IS?NOT?NULL?sal?>=?1000?AND?comm?IS?NOT?NULL?
如何編寫高效的SQL:?
當然要考慮sql常量的優化和操作符的優化啦,另外,還需要:?
1?合理的索引設計:?
例:表record有620000行,試看在不同的索引下,下面幾個SQL的運行情況:?
語句A?
SELECT?count(*)?FROM?record?
WHERE?date?>'19991201'?and?date?<?'19991214‘?and?amount?>2000?
語句B?
SELECT?count(*)?FROM?record?
WHERE?date?>'19990901'?and?place?IN?('BJ','SH')?
語句C?
SELECT?date,sum(amount)?FROM?record?
group?by?date?
1?在date上建有一個非聚集索引?
A:(25秒)?
B:(27秒)?
C:(55秒)?
分析:?
date上有大量的重復值,在非聚集索引下,數據在物理上隨機存放在數據頁上,在范圍查找時,必須執行一次表掃描才能找到這一范圍內的全部行。?
2?在date上的一個聚集索引?
A:(14秒)?
B:(14秒)?
C:(28秒)?
分析:?
在聚集索引下,數據在物理上按順序在數據頁上,重復值也排列在一起,因而在范圍查找時,可以先找到這個范圍的起末點,且只在這個范圍內掃描數據頁,避免了大范圍掃描,提高了查詢速度。?
3?在place,date,amount上的組合索引?
A:(26秒)?
C:(27秒)?
B:(<?1秒)?
分析:?
這是一個不很合理的組合索引,因為它的前導列是place,第一和第二條SQL沒有引用place,因此也沒有利用上索引;第三個SQL使用了place,且引用的所有列都包含在組合索引中,形成了索引覆蓋,所以它的速度是非常快的。?
4?在date,place,amount上的組合索引?
A:?(<?1秒)?
B:(<?1秒)?
C:(11秒)?
分析:?
這是一個合理的組合索引。它將date作為前導列,使每個SQL都可以利用索引,并且在第一和第三個SQL中形成了索引覆蓋,因而性能達到了最優。?
總結1?
缺省情況下建立的索引是非聚集索引,但有時它并不是最佳的;合理的索引設計要建立在對各種查詢的分析和預測上。一般來說:?
有大量重復值、且經常有范圍查詢(between,?>,<?,>=,<?=)和order?by、group?by發生的列,考慮建立聚集索引;?
經?常同時存取多列,且每列都含有重復值可考慮建立組合索引;在條件表達式中經常用到的不同值較多的列上建立檢索,在不同值少的列上不要建立索引。比如在雇員?表的“性別”列上只有“男”與“女”兩個不同值,因此就無必要建立索引。如果建立索引不但不會提高查詢效率,反而會嚴重降低更新速度。?
組合索引要盡量使關鍵查詢形成索引覆蓋,其前導列一定是使用最頻繁的列。?
2?避免使用不兼容的數據類型:?
例如float和INt、char和varchar、bINary和varbINary是不兼容的。數據類型的不兼容可能使優化器無法執行一些本來可以進行的優化操作。例如:?
SELECT?name?FROM?employee?WHERE?salary?>?60000?
在這條語句中,如salary字段是money型的,則優化器很難對其進行優化,因為60000是個整型數。我們應當在編程時將整型轉化成為錢幣型,而不要等到運行時轉化。?
3?IS?NULL?與IS?NOT?NULL:?
不?能用null作索引,任何包含null值的列都將不會被包含在索引中。即使索引有多列這樣的情況下,只要這些列中有一列含有null,該列就會從索引中排?除。也就是說如果某列存在空值,即使對該列建索引也不會提高性能。任何在WHERE子句中使用is?null或is?not?null的語句優化器是不允?許使用索引的。?
4?IN和EXISTS:?
EXISTS要遠比IN的效率高。里面關系到full?table?scan和range?scan。幾乎將所有的IN操作符子查詢改寫為使用EXISTS的子查詢。?
例子:?
語句1?
SELECT?dname,?deptno?FROM?dept?
WHERE?deptno?NOT?IN?
(SELECT?deptno?FROM?emp);?
語句2?
SELECT?dname,?deptno?FROM?dept?
WHERE?NOT?EXISTS?
(SELECT?deptno?FROM?emp?
WHERE?dept.deptno?=?emp.deptno);?
明顯的,2要比1的執行性能好很多?
因為1中對emp進行了full?table?scan,這是很浪費時間的操作。而且1中沒有用到emp的INdex,?
因為沒有WHERE子句。而2中的語句對emp進行的是range?scan。?
5?IN、OR子句常會使用工作表,使索引失效:?
如果不產生大量重復值,可以考慮把子句拆開。拆開的子句中應該包含索引。?
6?避免或簡化排序:?
應當簡化或避免對大型表進行重復的排序。當能夠利用索引自動以適當的次序產生輸出時,優化器就避免了排序的步驟。以下是一些影響因素:?
索引中不包括一個或幾個待排序的列;?
group?by或order?by子句中列的次序與索引的次序不一樣;?
排序的列來自不同的表。?
為了避免不必要的排序,就要正確地增建索引,合理地合并數據庫表(盡管有時可能影響表的規范化,但相對于效率的提高是值得的)。如果排序不可避免,那么應當試圖簡化它,如縮小排序的列的范圍等。?
7?消除對大型表行數據的順序存取:?
在?嵌套查詢中,對表的順序存取對查詢效率可能產生致命的影響。比如采用順序存取策略,一個嵌套3層的查詢,如果每層都查詢1000行,那么這個查詢就要查詢?10億行數據。避免這種情況的主要方法就是對連接的列進行索引。例如,兩個表:學生表(學號、姓名、年齡??)和選課表(學號、課程號、成績)。如果兩個?表要做連接,就要在“學號”這個連接字段上建立索引。?
還可以使用并集來避免順序存取。盡管在所有的檢查列上都有索引,但某些形式的WHERE子句強迫優化器使用順序存取。下面的查詢將強迫對orders表執行順序操作:?
SELECT?*?FROM?orders?WHERE?(customer_num=104?AND?order_num>1001)?OR?order_num=1008?
雖然在customer_num和order_num上建有索引,但是在上面的語句中優化器還是使用順序存取路徑掃描整個表。因為這個語句要檢索的是分離的行的集合,所以應該改為如下語句:?
SELECT?*?FROM?orders?WHERE?customer_num=104?AND?order_num>1001?
UNION?
SELECT?*?FROM?orders?WHERE?order_num=1008?
這樣就能利用索引路徑處理查詢。?
8?避免相關子查詢:?
一個列的標簽同時在主查詢和WHERE子句中的查詢中出現,那么很可能當主查詢中的列值改變之后,子查詢必須重新查詢一次。查詢嵌套層次越多,效率越低,因此應當盡量避免子查詢。如果子查詢不可避免,那么要在子查詢中過濾掉盡可能多的行。?
9?避免困難的正規表達式:?
MATCHES和LIKE關鍵字支持通配符匹配,技術上叫正規表達式。但這種匹配特別耗費時間。例如:SELECT?*?FROM?customer?WHERE?zipcode?LIKE?“98_?_?_”?
即使在zipcode字段上建立了索引,在這種情況下也還是采用順序掃描的方式。如果把語句改為SELECT?*?FROM?customer?WHERE?zipcode?>“98000”,在執行查詢時就會利用索引來查詢,顯然會大大提高速度。?
另外,還要避免非開始的子串。例如語句:SELECT?*?FROM?customer?WHERE?zipcode[2,3]?>“80”,在WHERE子句中采用了非開始子串,因而這個語句也不會使用索引。?
10?不充份的連接條件:?
例:表card有7896行,在card_no上有一個非聚集索引,表account有191122行,在account_no上有一個非聚集索引,試看在不同的表連接條件下,兩個SQL的執行情況:?
SELECT?sum(a.amount)?FROM?account?a,card?b?WHERE?a.card_no?=?b.card_no?
(20秒)?
將SQL改為:?
SELECT?sum(a.amount)?FROM?account?a,card?b?WHERE?a.card_no?=?b.card_no?and?a.account_no=b.account_no?
(<?1秒)?
分析:?
在第一個連接條件下,最佳查詢方案是將account作外層表,card作內層表,利用card上的索引,其I/O次數可由以下公式估算為:?
外層表account上的22541頁+(外層表account的191122行*內層表card上對應外層表第一行所要查找的3頁)=595907次I/O?
在第二個連接條件下,最佳查詢方案是將card作外層表,account作內層表,利用account上的索引,其I/O次數可由以下公式估算為:?
外層表card上的1944頁+(外層表card的7896行*內層表account上對應外層表每一行所要查找的4頁)=?33528次I/O?
可見,只有充份的連接條件,真正的最佳方案才會被執行。?
多表操作在被實際執行前,查詢優化器會根據連接條件,列出幾組可能的連接方案并從中找出系統開銷最小的最佳方案。連接條件要充份考慮帶有索引的表、行數多的表;內外表的選擇可由公式:外層表中的匹配行數*內層表中每一次查找的次數確定,乘積最小為最佳方案。?
不可優化的WHERE子句?
例1?
下列SQL條件語句中的列都建有恰當的索引,但執行速度卻非常慢:?
SELECT?*?FROM?record?WHERE?substrINg(card_no,1,4)='5378'?
(13秒)?
SELECT?*?FROM?record?WHERE?amount/30<?1000?
(11秒)?
SELECT?*?FROM?record?WHERE?convert(char(10),date,112)='19991201'?
(10秒)?
分析:?
WHERE子句中對列的任何操作結果都是在SQL運行時逐列計算得到的,因此它不得不進行表搜索,而沒有使用該列上面的索引;如果這些結果在查詢編譯時就能得到,那么就可以被SQL優化器優化,使用索引,避免表搜索,因此將SQL重寫成下面這樣:?
SELECT?*?FROM?record?WHERE?card_no?like?'5378%'?
(<?1秒)?
SELECT?*?FROM?record?WHERE?amount<?1000*30?
(<?1秒)?
SELECT?*?FROM?record?WHERE?date=?'1999/12/01'?
(<?1秒)?
11?存儲過程中,采用臨時表優化查詢:?
例?
1.從parven表中按vendor_num的次序讀數據:?
SELECT?part_num,vendor_num,price?FROM?parven?ORDER?BY?vendor_num?
INTO?temp?pv_by_vn?
這個語句順序讀parven(50頁),寫一個臨時表(50頁),并排序。假定排序的開銷為200頁,總共是300頁。?
2.把臨時表和vendor表連接,把結果輸出到一個臨時表,并按part_num排序:?
SELECT?pv_by_vn,*?vendor.vendor_num?FROM?pv_by_vn,vendor?
WHERE?pv_by_vn.vendor_num=vendor.vendor_num?
ORDER?BY?pv_by_vn.part_num?
INTO?TMP?pvvn_by_pn?
DROP?TABLE?pv_by_vn?
這?個查詢讀取pv_by_vn(50頁),它通過索引存取vendor表1.5萬次,但由于按vendor_num次序排列,實際上只是通過索引順序地讀?vendor表(40+2=42頁),輸出的表每頁約95行,共160頁。寫并存取這些頁引發5*160=800次的讀寫,索引共讀寫892頁。?
3.把輸出和part連接得到最后的結果:?
SELECT?pvvn_by_pn.*,part.part_desc?FROM?pvvn_by_pn,part?
WHERE?pvvn_by_pn.part_num=part.part_num?
DROP?TABLE?pvvn_by_pn?
這樣,查詢順序地讀pvvn_by_pn(160頁),通過索引讀part表1.5萬次,由于建有索引,所以實際上進行1772次磁盤讀寫,優化比例為30∶1。?
好了,搞定。?
其實sql的優化,各種數據庫之間都是互通的。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=629369