編寫高效優化的sql語句

1        引言

SQL語句是我們在軟件開發過程中操作數據庫經常要編寫的,SQL語句的寫法很靈活,同一個數據庫操作sql可能有好多種寫法,但是如何讓sql語句高效地執行,不給數據庫系統帶來更多的壓力,這個是我們應該學習並且掌握的。尤其是當一個很大的軟件系統操作數據庫很頻繁的時候,優化的sql語句顯得極其重要。本文檔主要真對日常軟件開發中常用到的一些數據庫的操作以及會涉及到的一些數據庫地知識、如何高效編寫sql語句進行了總結,供大家學習分享。

2        編寫高效地sql語句

2.1    儘量避免全表掃描

l  對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。(有明顯時間節省)

 

l  應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:

select id from t where num is null可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:select id from t where num=0()(有明顯時間節省)

 

l  應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。(5000條記錄的情況下節省0.016秒)

 

l  應儘量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:select id from t where num=10 or num=20,可以這樣查詢:

select id from twhere num=10

union all

select id from twhere num=20(表中5000條記錄的情況下節省大約0.02秒的時間)

 

l  in 和 not in 也要慎用,否則會導致全表掃描,如:

select id from twhere num in(1,2,3)對於連續的數值,能用between 就不要用 in 了:select id from t where num between 1 and 3(170條記錄的情況下速度加快0.016秒,5000條記錄的情況下節省一半的時間)

   

l  如果在 where 子句中使用參數,也會導致全表掃描。因爲SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作爲索引選擇的輸入項。如下面語句將進行全表掃描:

select id from t where num=@num可以改爲強制查詢使用索引:

select id from t with(index(索引名)) where num=@num

l  .應儘量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:select id from t where num/2=100應 改爲: 

select id from t where num=100*2(表達式計算會耗去一定時間)

 

l  應儘量避免在where子句中對字段進行函數 操作,這將導致引擎放棄使用索引而進行全表掃描。

 

l  不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。

 

2.2    關於索引的使用 

l  在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。(時間差別很明顯,儘量使用索引中的第一個字段查詢)

 

l  並不是所有索引對查詢都有效,SQL是根據表中 數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如一表中有字段***,male、female幾乎各一半,那麼即使在***上建了索引也對查詢效率起不了作用。

 

l  索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了insert 及 update 的效率,因爲 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。(索引會影響insert和update的效率)

 

l  應儘可能的避免更新 clustered 索引數據列,因爲clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那麼需要考慮是否應將該索引建爲 clustered 索引。

 

l  要注意索引的維護,週期性重建索引。

 

2.3    關於臨時表的使用

l  儘量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。

 

l  避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。

 

l  臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個數據集時。但是,對於一次性事件,最好使用導出表。

 

l  在新建臨時表時,如果一次性插入數據量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,爲了緩和系統表的資源,應先create table,然後insert。

 

l  如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

 

2.4    關於遊標的使用

l  儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該考慮改寫。

 

l  與臨時表一樣,遊標並不是不可使用。對小型數據集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。

 

l  使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。

 

2.5    一些關鍵字以及函數的使用

l  WHERE子句中的連接順序:

Oracle採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。

 

l  SELECT子句中避免使用‘*’:

Oracle在解析的過程中, 會將‘*’依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味着將耗費更多的時間。(時間相差很多,甚至達到一半)

 

l  使用DECODE函數來減少處理時間:

使用DECODE函數可以避免重複掃描相同記錄或重複連接相同的表。

 

l  儘量多使用COMMIT:

只要有可能,在程序中儘量多使用COMMIT, 這樣程序的性能得到提高,需求也會因爲COMMIT所釋放的資源而減少,COMMIT所釋放的資源:

a. 回滾段上用於恢復數據的信息。

b. 被程序語句獲得的鎖。

c. redo logbuffer 中的空間。

d. Oracle爲管理上述3種資源中的內部花費。

 

l  用Where子句替換HAVING子句:

避免使用HAVING子句,HAVING 只會在檢索出所有記錄之後纔對結果集進行過濾。這個處理需要排序,總計等操作。如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷。(非oracle中)on、where、having這三個都可以加條件的子句中,on是最先執行,where次之,having最後,因爲on是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,where也應該比having快點的,因爲它過濾數據後才進行sum,在兩個表聯接時才用on的,所以在一個表的時候,就剩下where跟having比較了。在這單表查詢統計的情況下,如果要過濾的條件沒有涉及到要計算字段,那它們的結果是一樣的,只是where可以使用rushmore技術,而having就不能,在速度上後者要慢如果要涉及到計算的字段,就表示在沒計算之前,這個字段的值是不確定的,根據上篇寫的工作流程,where的作用時間是在計算之前就完成的,而having就是在計算後才起作用的,所以在這種情況下,兩者的結果會不同。在多表聯接查詢時,on比where更早起作用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表後,再由where進行過濾,然後再計算,計算完後再由having進行過濾。由此可見,要想過濾條件起到正確的作用,首先要明白這個條件應該在什麼時候起作用,然後再決定放在那裏。(having一般和一些聚合函數使用,儘量避免,能用where代替的儘量用where)

 

l  通過內部函數提高SQL效率:

複雜的SQL往往犧牲了執行效率。能夠掌握上面的運用函數解決問題的方法在實際工作中是非常有意義的。

 

l  使用表的別名(Alias):

當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上。這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。(這點大家都應該能理解)

 

l  用EXISTS替代IN、用NOT EXISTS替代NOT IN:

在許多基於基礎表的查詢中,爲了滿足一個條件,往往需要對另一個表進行聯接。在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率。在子查詢中,NOT IN子句將執行一個內部的排序和合並。無論在哪種情況下,NOT IN都是最低效的 (因爲它對子查詢中的表執行了一個全表遍歷)。爲了避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS。

 

l  用EXISTS替換DISTINCT:

當提交一個包含一對多表信息(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT。一般可以考慮用EXIST替換, EXISTS 使查詢更爲迅速,因爲RDBMS核心模塊將在子查詢的條件一旦滿足後,立刻返回結果。

 

l  SQL語句用大寫的;因爲Oracle總是先解析SQL語句,把小寫的字母轉換成大寫的再執行。

 

l  在Java代碼中儘量少用連接符“+”連接字符串。(編程時要注意)

 

l  避免在索引列上使用NOT通常,我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函數相同的影響。當Oracle“遇到”NOT,他就會停止使用索引轉而執行全表掃描。(耗去一部分時間)

 

l  避免在索引列上使用計算。WHERE子句中,如果索引列是函數的一部分。優化器將不使用索引而使用全表掃描。(耗去一部分時間)

 

l  用UNION替換OR (適用於索引列):

通常情況下,用UNION替換WHERE子句中的OR將會起到較好的效果。對索引列使用OR將造成全表掃描。注意,以上規則只針對多個索引列有效。如果有column沒有被索引,查詢效率可能會因爲你沒有選擇OR而降低。在下面的例子中,LOC_ID 和REGION上都建有索引。

 

l  用WHERE替代ORDER BY:

ORDER BY 子句只在兩種嚴格的條件下使用索引。ORDER BY中所有的列必須包含在相同的索引中並保持在索引中的排列順序。 ORDER BY中所有的列必須定義爲非空。WHERE子句使用的索引和ORDER BY子句中所使用的索引不能並列。

 

l  優化GROUP BY:

提高GROUP BY 語句的效率,可以通過將不需要的記錄在GROUP BY 之前過濾掉。

 

l  用UNION-ALL 替換UNION ( 如果有可能的話):

當SQL語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併,然後在輸出最終結果前進行排序。如果用UNION ALL替代UNION,這樣排序就不是必要了。效率就會因此得到提高。需要注意的是,UNION ALL 將重複輸出兩個結果集合中相同記錄。因此各位還是要從業務需求分析使用UNION ALL的可行性。 UNION 將對結果集合排序,這個操作會使用到SORT_AREA_SIZE這塊內存。對於這塊內存的優化也是相當重要的。

 

3        高效sql語句舉例

3.1    表操作

l  最高效的刪除重複記錄方法:

 

DELETE FROM EMP E WHERE E.ROWID> (SELECT MIN(X.ROWID) FROM  EMP XWHERE X.EMP_NO = E.EMP_NO);

 

l  減少對錶的查詢,在含有子查詢的SQL語句中,要特別注意減少對錶的查詢。例子:

 

SELECT TAB_NAME FROM TABLES WHERE(TAB_NAME, DB_VER) =(SELECTTAB_NAME, DB_VER FROM TAB_COLUMNS WHERE VERSION =604)

 

3.2    關鍵字的使用

l  用EXISTS替代IN、用NOT EXISTS替代NOT IN:

 

(高效)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 WHERELOC = 'MELB')

 

l  用EXISTS替換DISTINCT:(有明顯的時間節省)

 

(低效):

 SELECT DISTINCT DEPT_NO, DEPT_NAME

   FROM DEPT D, EMP E

 WHERE D.DEPT_NO = E.DEPT_NO

(高效):

 SELECT DEPT_NO, DEPT_NAME

   FROM DEPT D

 WHERE EXISTS (SELECT 'X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);

 

l  避免在索引列上使用計算。WHERE子句中,如果索引列是函數的一部分。優化器將不使用索引而使用全表掃描。

 

(低效):

SELECT … FROM DEPT WHERE SAL *12 > 25000;

(高效):

SELECT … FROM DEPT WHERE SAL> 25000 / 12;

 

l  用>=替代>:

 

高效:SELECT* FROM  EMP  WHERE DEPTNO >=4

低效: SELECT* FROM EMP WHERE DEPTNO >3

 

l  用UNION替換OR (適用於索引列):

 

高效:SELECTLOC_ID, LOC_DESC, REGION

 FROM LOCATION

 WHERE LOC_ID = 10

UNION

SELECT LOC_ID, LOC_DESC, REGIONFROM LOCATION WHERE REGION = 'MELBOURNE'

低效: SELECTLOC_ID, LOC_DESC, REGION

 FROM LOCATION

 WHERE LOC_ID = 10

    OR REGION = 'MELBOURNE'

 

l  用IN來替換OR:

 

低效:

SELECT…. FROM LOCATION WHERELOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30

高效:

SELECT… FROM LOCATION WHERELOC_IN IN (10,20,30);

 

l  優化GROUP BY:

 

低效: SELECTJOB , AVG(SAL) FROM EMP GROUP JOB HAVING JOB = 'PRESIDENT' OR JOB = 'MANAGER'

高效: SELECTJOB , AVG(SAL) FROM EMP WHERE JOB = 'PRESIDENT' OR JOB = 'MANAGER' GROUP JOB

 

 

l  用WHERE替代ORDER BY:

 

低效: (索引不被使用)

SELECT DEPT_CODE FROM DEPT ORDERBY DEPT_TYPE

高效: (使用索引)

SELECT DEPT_CODE FROM DEPT WHEREDEPT_TYPE > 0

 

4        總結

以上是根據查閱資料以及平時使用sql語句的經驗總結的一些技巧和方法,可能不夠全面也不是完全準確,希望大家閱讀之後都會有收穫,在平時開發的過程中能高效地開發。另外,sql語句是比較靈活的,在開發的過程中也希望大家能根據實際情況來進行靈活編寫,不一定完全按照文檔中所描述的方法,謝謝!

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章