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

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
