Oracle數據庫的sql語句性能優化

  在應用系統開發初期,因爲開發數據庫數據比較少,對於查詢sql語句,複雜試圖的編寫等體會不出sql語句各類寫法的性能優劣,可是若是將應用系統提交實際應用後,隨着數據庫中數據的增長,系統的響應速度就成爲目前系統須要解決的最主要問題之一。系統優化中一個很重要的方面就是sql語句的優化。對於海量數據,劣質sql語句和優質sql語句之間的速度差異能夠達到上百倍,可見對於一個系統不是簡單地能實現其功能就行,而是要寫出高質量的sql語句,提升系統的可用性。程序員

  Oracle的sql調優第一個複雜的主題,甚至須要長篇概論來介紹OracleSQL調優的細微差異。不過有一些基本的規則是每一個OracleDBA都須要聽從的,這些規則能夠改善他們系統的性能。sql

  sql調優的目標是簡單的:消除沒必要要的大表全表搜索。沒必要要的全表搜索致使大量沒必要要的磁盤I/O,從而拖慢整個數據庫的性能,對於沒必要要的全表搜索來講,最多見的調優方法是增長索引,能夠在表中加入標準的B樹索引,也能夠加入位圖索引和基於函數的索引。要決定是否消除一個全表搜索,你能夠仔細檢查索引搜索的I/O開銷和全表搜索的開銷,它們的開銷和數據塊的讀取和可能的並行執行有關,並將二者做對比。數據庫

  另外,在全表搜索是一個最快的訪問方法時,將小表的全表搜索放到緩存(內存)中,也是一個很是明智的選擇。咱們會發現如今誕生了不少基於內存的數據庫管理系統,將整個數據庫置於內存之中,性能將獲得質的飛躍。緩存

1、與索引相關的性能優化

  在多數狀況下,Oracle使用索引來更快地遍歷表,優化器主要根據定義的索引來提升性能。可是,若是在sql語句的where子句中寫的sql代碼不合理,就會形成優化器刪去索引而使用全表掃描,通常這種sql語句就是所謂的劣質sql語句。在編寫sql語句時咱們應清楚優化器根據何種原則來刪除索引,這有助於寫出高性能的sql語句。性能優化

1.IS NULL 與 IS NOT NULL

  不能用null作索引,任何包含null值的列都將不會被包含在索引中,即便索引有多列這樣的狀況下,只要這些列中有一列含有null,該列就會從索引中排除。也就是說某列存在空值,即便對該列創建索引也不會提升性能。任何在where子句中使用is null或者is nou null的語句優化器是不容許使用索引的。服務器

2.聯接列

  對於有聯接的列,即便最後的聯接列爲一個靜態值,優化器是不會使用索引的。來看個例子,假定有一個職工表(employee),對於一個職工的姓和名分紅兩列存放(FIRST_NAME和LAST_NAME),如今要查詢一個叫Beill Cliton的職工。session

  下面是一個採用聯接查詢的sql語句:多線程

select * from employee where first_name ||''|| last_name = 'Beill Cliton';

  上面這條語句徹底能夠查詢出是否有Beill Cliton這個員工,可是這裏須要注意,系統優化器對基於LAST_NAME建立的索引沒有使用,當採用下面這種sql語句的編寫,Oracle系統就能夠採用基於LAST_NAME建立的索引:oracle

select * from employee where first_name = 'Beill' and last_name = 'Cliton';

3.帶通配符(%)的like語句

  一樣拿上面的例子,目前的需求是這樣的,要求在職工表中查詢名字中包含Cliton的人,能夠採用以下的查詢sql語句:函數

select * from employee where last_name like '%Cliton%';

  這裏因爲通配符(%)在搜尋詞首出現,因此Oracle系統沒法使用last_name的索引。在不少狀況下可能沒法避免這種狀況,可是必定要內心有底,通配符這樣使用會下降查詢速度。然而當通配符出如今字符串其它位置時,優化器就能利用索引,在下面的查詢中索引就獲得了使用:

select * from employee where last_name like 'C%';

  該語句查詢全部姓名以C開頭的,這徹底知足索引的要求,由於索引自己就是一個排序的列。

4.ORDER BY 子句

  ORDER BY子句決定了Oracle如何將返回的查詢結果排序。該子句對要排序的列沒有什麼特別的限制,也能夠將函數加入到列中(象聯接或者附加等)。任何在該子句的非索引項或者有計算表達式都將下降查詢速度。

  仔細檢查order by子語句找出非索引項或者表達式,它們會下降性能。解決這個問題的辦法就是重寫order by子句以使用索引,也能夠爲所使用的列創建另一個索引,同時應絕對避免在order by子句中使用表達式。

5.NOT 關鍵字

  咱們在查詢時常常在where子句使用一些邏輯表達式,如大於、小於、等於以及不等於等等,也可使用and(與)、or(或)以及not(非)。NOT可用來對任何邏輯運算取反。下面是一個NOT子句的例子:

... where not(status = 'VALID');

  若是要使用NOT,則應在取反的短語前面加上括號,並在短語前面加上NOT運算符。NOT運算符包含在另一個邏輯運算符中,這就是不等於(<>)運算符。換句話說,即便不在查詢where子句中加入NOT關鍵字,NOT仍在運算符中,見下例:

... where status <> 'VALID';

  再看下面這個例子:

select * from employee where salary <> 3000;

  對這個查詢,能夠改寫爲不使用NOT:

select * from employee where salary < 3000 or salary > 3000;

  雖然這兩種查詢的結果是同樣的,可是第二種查詢方案會比第一種查詢方案更快些。第二種查詢容許Oralce對salary列使用索引,而第一種查詢不能使用索引。

6.IN 和 EXISTS

  有時候會將一列和一系列值相比較,最簡單的辦法就是在where子句中使用子查詢。在where子句中可使用兩種格式的子查詢。

  第一種格式是使用IN操做符:

... where column in (select * from ... where ...);

  第二種格式是使用EXISTS操做符:

... where exists (select 'X' from ... where ...);

  相信絕大多數人會使用第一種格式,由於它比較容易編寫,而實際上第二種格式要遠比第一種格式的效率高。在Oracle中能夠幾乎將全部的IN操做符子查詢改寫爲使用EXISTS的子查詢。

  第二種格式中,子查詢以「select 'X'」開始,運用EXISTS子句,無論子查詢從表中抽取什麼數據,它只查看where子句,這樣優化器就沒必要遍歷整個表而僅根據索引就可完成工做(這裏假定在where語句中使用的列存在索引)。相對於IN子句來講,EXISTS使用相連子查詢構造起來要比IN子查詢困難一些。

  經過使用EXISTS,Oracle系統會首先檢查主查詢,而後運行子查詢直到它找到第一個匹配項,這就節省了時間。Oralce系統在執行IN子查詢時,首先執行子查詢,並將得到的結果列表放在一個加了索引的臨時表中。在執行子查詢以前,系統先將主查詢掛起,待子查詢執行完畢,存放在臨時表中之後再執行主查詢。這也就是使用EXISTS比使用IN一般查詢速度快的緣由。

  同時應儘量使用NOT EXISTS來代替NOT IN,儘管兩者都使用了NOT(不能使用索引而下降速度),NOT EXISTS要比NOT IN查詢效率更高。

7.<> 不等於符號

  不等於操做符是永遠不會用到索引的,由於對它的處理只會產生全表掃描。

  推薦方案:用其它相同功能的操做運算符代替,如

  a<>0 改成 a>0 or a<0,  a<>'' 改成 a >''

8.避免在索引列上使用計算

  WHERE子句中,若是索引列是函數的一部分,優化器將不使用索引而使用全表掃描

  低效:

select ... from dept where SAL * 12 > 25000;

  高效:

select ... from dept where SAL > 25000/12;

9.老是使用索引的第一個列

  若是索引是創建在多個列上,只有在它的第一個列(leading column)被where子句引用時,優化器纔會選擇使用該索引。這也是一條簡單而重要的規則,當僅引用索引的第二個列時,優化器使用了全表掃描而忽略了索引。

10.避免改變索引列的類型

  當比較不一樣數據類型的數據時,Oralce自動對列進行簡單的類型轉換。

  假設empno是一個數值類型的索引列:

select ... from emp where empno = '123';

  實際上,通過了Oracle類型轉換,語句轉化爲:

select ... from emp where empno = TO_NUMBER('123');

  幸運的是,類型轉換沒有發生在索引列上,索引的用途沒有被改變。

  如今,假設emp_type是一個字符類型的索引列:

select ... from emp where emp_type = 123;

  這個語句被Oracle轉換爲:

select ... from emp where TO_NUMBER(emp_type) = 123;

  由於內部發生的類型轉換,這個索引將不會被用到。爲了不Oracle對sql進行隱式的類型轉換,最好把類型轉換用顯示錶現出來。注意當字符和數值比較時,Oracle會優先轉換數值類型到字符類型。

11.須要小心的 WHERE 子句

  某些select語句中的where子句不使用索引:

  • '!='將不使用索引,索引只能告訴你什麼存在於表中,而不能告訴你什麼不存在於表中
  • '||'是字符鏈接函數,就像其它函數那樣,停用了索引
  • '+'是數學函數,就像其它數學函數那樣,停用了索引
  • 相同的索引列不能互相比較,這將會啓動全表掃描

12.其它一些規則

  • 若是檢索數據量超過30%的表中記錄數,使用索引將沒有顯著的效率提升
  • 在特定狀況下,使用索引也許會比全盤掃描慢,但這是同一個數量級上的區別。而一般狀況下,使用索引比全表掃描要快幾倍乃至幾千倍
  • 避免在索引列上使用IS NULL 和IS NOT NULL
  • 避免在索引列上使用NOT
  • 用EXISTS替代IN、用NOT EXISTS替代NOT IN
  • 經過內部函數提升sql效率
  • 選擇最有效的表名順序:Oracle的解析器按照從右到左的順序處理from子句中的表名,from子句中寫在最後的表(基礎表)將被最早處理,在from子句中包含多個表的狀況下,你必須選擇記錄條數最少的表做爲基礎表。若是有三個以上的錶鏈接查詢,那就須要選擇交叉表做爲基礎表,交叉表是指被其它表所引用的表
  • WHERE子句中的鏈接順序:Oracle採用自下而上的順序解析where子句,根據這個原理,表之間的鏈接必須寫在其它where條件以前,那些能夠過濾掉最大數量記錄的條件必須寫在where子句的末尾

2、與內存相關的優化

1.UNION 操做符

  UNION在進行表連接後會篩選掉重複的記錄,因此在表連接後會對所產生的結果集進行排序運算,刪除重複的記錄再返回結果。實際大部分應用中是不會產生重複的記錄。最多見的是過程表與歷史表UNION。如:

select * from A union select * from B;

  這個sql在運行時先取出兩個表的結果,再用排序空間進行排序刪除重複的記錄,最後返回結果集,若是表數據量大的話可能會致使用磁盤進行排序。

  推薦方案:採用UNION ALL操做符替代UNION,由於UNION ALL操做只是簡單的將兩個結果合併後就返回:

select * from A union all select * from B;

2.SQL書寫的影響

  同一功能同一性能不一樣寫法sql的影響

  如,一個sql在A程序員寫的爲:

select * from employee;

  B程序員寫爲:

select * from scott.employee; (帶表全部者的前綴)

  C程序員寫爲:

select * from EMPLOYEE; (大寫表名)

  D程序員寫爲:

select *   from employee; (中間多了空格)

  以上四個sql在Oracle分析整理以後產生的結果及執行的時間是同樣的,可是從Oracle共享內存SGA的原理,能夠得出Oracle對每一個sql都會對其進行一次分析,而且佔用共享內存,若是將sql的字符串及格式寫得徹底相同則Oracle只會分析一次,共享內存也只會留下一次的分析結果,這不只能夠減小分析sql的時間,並且也能夠減小共享內存重複的信息,oracle也能夠準確統計sql的執行頻率。

3.避免在磁盤中排序

  當與Oracle創建起一個session時,在內存中就會爲該session分配一個私有的排序區域。若是該鏈接是一個專用的鏈接(dedicated connection),那麼就會根據init.ora中sort_area_size參數的大小在內存中分配一個Program Global Area(PGA)。若是鏈接是經過多線程服務器創建的,那麼排序的空間就在large_pool中分配。不幸的是,對於全部的session,用做排序的內存量都必須是同樣的,咱們不能爲須要更大排序的操做分配額外的排序區域。所以,設計者必須作出一個平衡,在分配足夠的排序區域以免發生大的排序任務時出現磁盤排序(disksorts)的同時,對於那些並不須要進行很大排序的任務,就會出現一些浪費。固然,當排序的空間需求超出了sort_area_size的大小時,這時將會在TEMP表空間中分頁進行磁盤排序。磁盤排序要比內存排序大概慢14000倍。

  上面咱們已經提到,私有排序區域的大小是由init.ora中的sort_area_size參數決定的。每一個排序所佔用的大小由init.ora中的sort_area_size參數決定。當排序不能在分配的空間中完成時,就會使用磁盤排序的方式,即在Oracle實例中的臨時表空間中進行。

  磁盤排序的開銷是很大的,有幾個方面的緣由。首先,和內存排序相比較,它們特別慢;並且,磁盤排序會消耗臨時表空間中的資源。Oracle還必須分配緩衝池塊來保持臨時表空間中的塊。不管何時,內存排序都比磁盤排序好,磁盤排序將會令任務變慢,而且會影響Oracle實例的當前任務的執行。還有,過多的磁盤排序將會令freebufferwaits的值特別高,從而令其它任務的數據塊由緩衝中移走。

4.避免使用耗費資源的操做

  帶有 DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的sql語句回啓動sql引擎執行耗費資源的排序(SORT)功能。DISTINCT須要一次排序操做,而其它的至少須要執行兩次排序。一般,帶有UNION,MINUS,INTERSECT的sql語句均可以用其它方式重寫。若是你的數據庫的sort_area_size調配的好,使用UNION,MINUS,INTERSECT也是能夠考慮的,畢竟它們的可讀性很強。

3、其它性能優化相關技巧

1.刪除重複記錄

  最高效的刪除重複記錄方法(因使用了ROWID)例子:

delete from emp E where E.ROWID > (select MIN(X.ROWID) from emp X where X.emp_no = E.emp_no);

2.用 TRUNCATE 替代 DELETE

  當刪除表中的記錄時,在一般狀況下,回滾段(rollback segments)用來存放能夠被恢復的信息。若是你沒有COMMIT事務,Oracle會將數據恢復到刪除以前的狀態(準確地說是恢復到執行刪除以前的狀態),而當運用TRUNCATE時,回滾段再也不存聽任何可被恢復的信息。當命令運行後,數據不能被恢復,所以不多的資源被調用,執行時間也會很短(TRUNCATE只在清空全表適用,TRUNCATE是DDL而不是DML)。

3.SELECT 子句中避免使用 *

  Oracle在解析的過程當中,會將 * 依次轉換成全部的列名,這個工做是經過查詢數據字典完成的,這意味着將耗費更多的時間。

4.用 WHERE 子句替換 HAVING 子句

  避免使用having子句,having只會在檢索出全部記錄以後纔對結果集進行過濾,這個處理須要排序、總計等操做。若是能經過where子句限制記錄的數目,那就能減小這方面的開銷。sql語句中on、where、having這三個均可以加條件的子句中,on是最早執行,where次之,having最後,由於on是先把不符合條件的記錄過濾後才進行統計,它就能夠減小中間運算要處理的數據,按理說應該是速度最快的,where也應該比having快點的,由於它過濾數據後才進行sum,在兩個錶鏈接時採用on,因此在一個表的時候,就剩下where跟having比較了。在單表查詢統計的狀況下,若是要過濾的條件沒有涉及到要計算字段,那它們的結果是同樣的,只是where可使用rushmore技術,而having就不能,在速度上後者要慢若是要涉及到計算的字段,就表示在沒計算以前,這個字段的值是不肯定的,where的做用時間是在計算以前就完成的,而having就是在計算以後才起做用的,因此在這種狀況下,二者的結果會不一樣。在多表聯接查詢時,on比where更早起做用,系統首先根據各個表之間的鏈接條件,把多個表合成一個臨時表後,再由where進行過濾,而後再計算,計算完成後再由having進行過濾。因而可知,要想過濾條件起到正確的做用,首先要明白這個條件應該在何時起做用,而後再決定放在哪裏。

5.使用表的別名(Alias)

  當在sql語句中鏈接多個表時,請使用表的別名並把別名前綴於每一個column上。這樣一來,就能夠減小解析的時間並減小那些由column歧義引發的語法錯誤。

6.用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN

  在許多基於基礎表的查詢中,爲了知足一個條件,每每須要對另外一個表進行聯接。在這種狀況下,使用EXISTS(或NOT EXISTS)一般將提升查詢的效率。在子查詢中NOT IN子句將執行一個內部的排序和合並。不管在哪一種狀況下,NOT IN都是最低效的(由於它對子查詢中的表執行了一個全表遍歷)。爲了不使用NOT IN,咱們能夠把它改寫成外聯接(OUTER JOIN)或NOT EXISTS。

  高效:

select * from emp where empno > 0 and EXISTS (select 'X' from dept where dept.deptno = emp.deptno and loc = 'MELB');

  低效:

select * from emp where empno > 0 and deptno IN (select deptno from dept where loc = 'MELB');

7.用 EXISTS 替換 DISTINCT

  提交一個對多表信息(好比部門表和僱員表)進行查詢的語句時,避免在select子句中使用distinct。通常能夠考慮用EXISTS替換,EXISTS使查詢更爲迅速,由於RDBMS核心模塊將在子查詢的條件一但知足後,馬上返回結果。

  高效:

select dept_no, dept_name from dept D where EXISTS (select 'X' from emp E where E.deptno = D.deptno);

  低效:

select DESTINCT dept_no, dept_name from dept D, emp E where D.deptno = E.deptno;

8.SQL語句使用大寫

  由於Oracle老是先解析sql語句,把小寫的字母轉換成大寫的再執行

9.用 >= 替代 >

  高效:

select * from emp where deptno >= 4;

  低效:

select * from emp where deptno > 3;

  二者的區別在於,前者DBMS將直接跳到第一個deptno等於4的記錄,然後者將首先定位到deptno=3的記錄而且向前掃描到第一個deptno大於3的記錄。

10.優化 GROUP BY

  提升group by語句的效率,能夠經過將不須要的記錄在group by以前過濾掉。下面兩個查詢返回相同結果,但第二個就明顯快了許多。

  低效:

select job, AVG(SAL) from emp group by job having job = 'PRESIDENT' or job = 'MANAGER';

  高效:

select job, AVG(SAL) from emp where job = 'PRESIDENT' or job = 'MANAGER' group by job;
相關文章
相關標籤/搜索