1. 程式人生 > >資料庫sql優化規則

資料庫sql優化規則

1. 選擇最有效率的表名順序 

按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表 driving table)將被最先處理. 在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表.當DB2處理多個表時,會運用排序及合併的方式連線它們.首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行派序,然後掃描第二個表(FROM子句中最後第二個表),最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併.

例如:

表 TAB1 16,384 條記錄

表 TAB2 1 條記錄 

選擇TAB2作為基礎表 (最好的方法)

select count(*) from tab1,tab2 執行時間0.96秒 

選擇TAB1作為基礎表 (不佳的方法)

select count(*) from tab2,tab1 執行時間26.09秒

如果有3個以上的表連線查詢,那就需要選擇交叉表(intersection table)作為基礎表,交叉表是指那個被其他表所引用的表.

例如:

EMP表描述了LOCATION表和CATEGORY表的交集.

 SELECT * FROM LOCATION L , CATEGORY C, EMP E WHERE E.EMP_NO BETWEEN 1000 AND 2000 AND E.CAT_NO = C.CAT_NO AND E.LOCN = L.LOCN

將比下列SQL更有效率 

SELECT * FROM EMP E , LOCATION L , CATEGORY C WHERE E.CAT_NO = C.CAT_NO  AND E.LOCN = L.LOCN AND E.EMP_NO BETWEEN 1000 AND 2000

2. WHERE子句中的連線順序

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

例如:

(低效,執行時間156.3秒)

SELECT …  FROM EMP E  WHERE SAL > 50000  AND JOB = ‘MANAGER' AND 25 < (SELECT COUNT(*) FROM EMP  WHERE MGR=E.EMPNO);

(高效,執行時間10.6秒)

SELECT …  FROM EMP E  WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)  AND SAL > 50000  AND JOB = ‘MANAGER';

3. SELECT子句中避免使用 ‘ * ‘ 

   當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用 ‘*'是一個方便的方法.不幸的是,這是一個非常低效的方法.實際上,DB2在解析的過程中,會將'*'依次轉換成所有的列名,這個工作是通過查詢資料字典完成的,這意味著將耗費更多的時間.

4. 整合簡單,無關聯的資料庫訪問 
如果你有幾個簡單的資料庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關係)例如:

SELECT NAME  FROM EMP  WHERE EMP_NO = 1234;

SELECT NAME  FROM DPT  WHERE DPT_NO = 10 ;

SELECT NAME  FROM CAT  WHERE CAT_TYPE = ‘RD';

上面的3個查詢可以被合併成一個:

SELECT E.NAME , D.NAME , C.NAME  FROM CAT C , DPT D , EMP E,DUAL X

WHERE NVL(‘X',X.DUMMY) = NVL(‘X',E.ROWID(+)) AND NVL(‘X',X.DUMMY) = NVL(‘X',D.ROWID(+)) AND NVL(‘X',X.DUMMY) = NVL(‘X',C.ROWID(+)) AND E.EMP_NO(+) = 1234 AND D.DEPT_NO(+) = 10  AND C.CAT_TYPE(+) = ‘RD';

注:oracle :(+)外連結;左右查詢,放在等號的左邊就是左查詢,放到右邊就是右查詢

        oracle : NVL函式的格式如下:NVL(expr1,expr2)  如果oracle第一個引數為空那麼顯示第二個引數的值,如果第一個引數的值不為空,則顯示第一個引數本來的值。

        ROWID 是一個類似於rownum的偽列,用於定位資料庫中一條記錄的一個相對唯一地址值。使用ROWID來進行單記錄定位速度是最快的

5. 儘量多使用COMMIT 

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

6. 用Where子句替換HAVING子句 

避免使用HAVING子句, HAVING只會在檢索出所有記錄之後才對結果集進行過濾.這個處理需要排序,總計等操作.如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷.

例如:

低效: SELECT REGION,AVG(LOG_SIZE)  FROM LOCATION  GROUP BY REGION HAVING REGION REGION != ‘SYDNEY'   AND REGION != ‘PERTH'

高效 

SELECT REGION,AVG(LOG_SIZE)  FROM LOCATION  WHERE REGION REGION != ‘SYDNEY'  AND REGION != ‘PERTH'  GROUP BY REGION

7. 減少對錶的查詢 
在含有子查詢的SQL語句中,要特別注意減少對錶的查詢. 

例如:

低效 

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

高效 

SELECT TAB_NAME  FROM TABLES  WHERE (TAB_NAME,DB_VER)

= ( SELECT TAB_NAME,DB_VER  FROM TAB_COLUMNS  WHERE VERSION = 604) Update 多個Column 例子:

低效:

UPDATE EMP

SET EMP_CAT = (SELECT MAX(CATEGORY) FROM EMP_CATEGORIES),

SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES)

WHERE EMP_DEPT = 0020;

高效:

UPDATE EMP   SET (EMP_CAT, SAL_RANGE)= (SELECT MAX(CATEGORY) , MAX(SAL_RANGE)  FROM EMP_CATEGORIES)  WHERE EMP_DEPT = 0020;

8. 使用表的別名(Alias) 

當在SQL語句中連線多個表時,請使用表的別名並把別名字首於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤.

9. 用EXISTS替代IN 

在許多基於基礎表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯接.在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率. 

低效:

SELECT *  FROM EMP (基礎表)  WHERE EMPNO > 0 AND DEPTNO IN (SELECT DEPTNO  FROM DEPT  WHERE LOC = ‘MELB')

高效:

SELECT *  FROM EMP (基礎表)  WHERE EMPNO > 0  AND EXISTS (SELECT ‘X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB')

10. 用NOT EXISTS替代NOT IN 

在子查詢中,NOT IN子句將執行一個內部的排序和合並.無論在哪種情況下,NOT IN都是最低效的 (因為它對子查詢中的表執行了一個全表遍歷).為了避免使用NOT IN ,我們可以把它改寫成外連線(Outer Joins)或NOT EXISTS.

例如:

SELECT … FROM EMP  WHERE DEPT_NO NOT IN (SELECT DEPT_NO  FROM DEPT
WHERE DEPT_CAT='A');

為了提高效率.改寫為:

(方法一: 高效)

SELECT ….  FROM EMP A,DEPT B  WHERE A.DEPT_NO = B.DEPT(+)

AND B.DEPT_NO IS NULL  AND B.DEPT_CAT(+) = ‘A'

(方法二: 最高效)

SELECT …. FROM EMP E  WHERE NOT EXISTS (SELECT ‘X'  FROM DEPT D

WHERE D.DEPT_NO = E.DEPT_NO  AND DEPT_CAT = ‘A');

11. 用表連線替換EXISTS 

通常來說 , 採用表連線的方式比EXISTS更有效率 

SELECT ENAME  FROM EMP E  WHERE EXISTS (SELECT ‘X' FROM DEPT  WHERE DEPT_NO = E.DEPT_NO  AND DEPT_CAT = ‘A');

(更高效)

SELECT ENAME  FROM DEPT D,EMP E  WHERE E.DEPT_NO = D.DEPT_NO AND DEPT_CAT = ‘A' ;

12. 用EXISTS替換DISTINCT 

當提交一個包含一對多表資訊(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT.一般可以考慮用EXIST替換 

例如:

低效:

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);

EXISTS 使查詢更為迅速,因為RDBMS核心模組將在子查詢的條件一旦滿足後,立刻返回結果.

13. 用>=替代> 

如果DEPTNO上有一個索引,

高效:

SELECT *  FROM EMP  WHERE DEPTNO >=4

低效:

SELECT *  FROM EMP  WHERE DEPTNO >3

兩者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄.

14. 多個平等的索引

當SQL語句的執行路徑可以使用分佈在多個表上的多個索引時, DB2會同時使用多個索引並在執行時對它們的記錄進行合併,檢索出僅對全部索引有效的記錄.在DB2選擇執行路徑時,唯一性索引的等級高於非唯一性索引.然而這個規則只有 當WHERE子句中索引列和常量比較才有效.如果索引列和其他表的索引類相比較.這種子句在優化器中的等級是非常低的.如果不同表中兩個相同等級的索引將被引用, FROM子句中表的順序將決定哪個會被率先使用. FROM子句中最後的表的索引將有最高的優先順序.如果相同表中兩個想同等級的索引將被引用, WHERE子句中最先被引用的索引將有最高的優先順序.

舉例:

DEPTNO上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.

SELECT ENAME, FROM EMP  WHERE DEPT_NO = 20  AND EMP_CAT = ‘A';

這裡,DEPTNO索引將被最先檢索,然後同EMP_CAT索引檢索出的記錄進行合併.執行路徑如下:

TABLE ACCESS BY ROWID ON EMP  AND-EQUAL INDEX RANGE SCAN ON DEPT_IDX

INDEX RANGE SCAN ON CAT_IDX

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

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

舉例:

低效: 

SELECT … FROM DEPT  WHERE SAL * 12 > 25000;

高效:

SELECT … FROM DEPT  WHERE SAL > 25000/12;

16. 避免在索引列上使用NOT 

通常, 我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函式相同的 
影響. 當DB2”遇到”NOT,他就會停止使用索引轉而執行全表掃描.

舉例:

低效: (這裡,不使用索引)

SELECT … FROM DEPT  WHERE DEPT_CODE NOT = 0;

高效: (這裡,使用了索引)

SELECT … FROM DEPT  WHERE DEPT_CODE > 0;

需要注意的是,在某些時候, DB2優化器會自動將NOT轉化成相對應的關係操作符.

NOT > to <=

NOT >= to <

NOT < to >=

NOT <= to >

17、歸納一下一些不使用索引的情況 
1,NOT  IN
2,NOT  BETWEEN
3,LIKE(第一個字元非%號除外,如name like '李%') 
4,<>
5,IS  NULL  /  IS  NOT  NULL
6,查詢的欄位加函式 
7,多欄位的組合索引(A,B,C),select * from ** where  B='33',則索引也不會用。(按字首式規則使用索引除外,如 A='33'  and  B='33' || A='33' || A='33'  and  C='33' ) 

18、查詢語句比較優化的寫法: 
1,用db2expln 評估其執行路徑
2,可能使用exist 的地方就儘量不用 IN,可以使用not  exist的地方,儘量不要用not in
3,兩個表進行JION時,大表放在前面,JION欄位建索引 
4,儘量用其它寫法,取代NOT  IN, 如 a, b表同結構,資料量很大,則代替select * from  a  where  a.c  not  in  (select  c  from b )
的語句有 
a)select  a.*  from  a, b  where  a.c  = b.c + and  b.c  is  null(據說速度比原寫法提高30倍 ) 
b)select * from a minus select a.* from a,b where a.c=b.c (速度其次)
c)select * from a where not exist(select a.* from a,b where a.c=b.c) (也不錯)


5,動態 SQL 中,儘量多用 execute  immediate,

6,對於很複雜的查詢語句,可以建立臨時表進行緩衝(關於臨時表的解釋與使用,還希望同行告訴我在哪裡有……) 

7,COUNT(*) 與 COUNT(某列)一樣進行全表掃描 Fast  Full  Index  Scan,速度差不多 

8,經常同時存取多列,或經常使用 GROUP  BY 的SQL語句,最好對錶的GROUP欄位建立組合索引。組合索引要儘量使關鍵查詢形成索引覆蓋,其前導列一定是使用最頻繁的列。 

9,對於欄位取值單一(如性別欄位只有男與女),而經常在性別上做查詢,建索引應該注意

假設你設定了一個非常好的索引,任何傻瓜都知道應該使用它,但是DB2 卻偏偏不用,那麼,需要做的第一件事情,是審視你的 sql 語句。 

DB2 要使用一個索引,有一些最基本的條件: 
1, where 子句中的這個欄位,必須是複合索引的第一個欄位; 
2, where 子句中的這個欄位,不應該參與任何形式的計算 

具體來講,假設一個索引是按 f1, f2, f3的次序建立的,現在有一個 sql語句, where子句是 f2 = : var2,則因為 f2不是索引的第1個欄位,無法使用該索引。 

第2個問題,則在我們之中非常嚴重。以下是從實際系統上面抓到的幾個例子: 

Select jobid  from  mytabs where isReq='0' and updatedate >= 2001-7-18', 'YYYY-MM-DD'; 

………

以上的例子能很容易地進行改進。請注意這樣的語句每天都在我們的系統中執行,消耗我們有限的cpu和 記憶體資源。 

除了1,2這兩個我們必須牢記於心的原則外,還應儘量熟悉各種操作符對 DB2是否使用索引的影響。這裡我只講哪些操作或者操作符會顯式(explicitly)地阻止 DB2使用索引。以下是一些基本規則: 

1, 如果 f1和 f2 是同一個表的兩個欄位,則 f1>f2, f1>=f2, f1
2, f1 is null,  f1  is  not  null,  f1  not  in,

f1  !=,  f1  like  ‘%pattern%’;
3, Not  exist
4, 某些情況下,f1  in  也會不用索引 

對於這些操作,別無辦法,只有儘量避免。比如,如果發現你的 sql 中的 in 操作沒有使用索引,也許可以將  in  操作改成比較操作 +  union  all。筆者在實踐中發現很多時候這很有效。