大家都在討論關於資料庫最佳化方面的東東,剛好參與開發了一個資料倉儲方面的項目,以下的一點東西算是資料庫最佳化方面的學習+實戰的一些心得體會了,拿出來大家共用。歡迎批評指正阿!
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上建有索引,但是在上面的語句中最佳化器還是使用順序存取路徑掃描整個表。因為這個語句要檢索的是分離的行的集合,所以應該改為如下語句: