Oracle PL/SQL高級編程(第八彈:性能優化:SQL語句性能優化)

連接查詢的表順序

默認情況下,當對多個表進行連接查詢時,Oracle分析器會按照從右到左的順序處理FROM子句中的表名。例如下面的語句:

SELECT a.empno, a.ename,c.deptno, c.dname, a.log_action
  FROM emp_log a, emp b, dept c;

在執行時,Oracle會先查詢dept表,根據dept表查詢的行作爲數據源串行連接emp表繼續執行,因此dept表又稱爲基礎表或驅動表。由於連接的順序對於查詢的效率有非常大的影響,因此在處理多表連接時,必須選擇記錄條數較少的表作爲基礎表,Oracle會使用排序與合併的方式進行連接。比如先掃描dept表,然後對dept表進行排序,再掃描emp表,最後將所有檢索出來的記錄與第一個表中的記錄進行合併。
如果有3個以上的表連接查詢,就需要選擇交叉表(Intersection Table)作爲基礎表。交叉表是指哪個被其他表所引用的表。由於emp_log表時dept表和emp表的交叉表,即包含dept的內容又包含emp的內容,因此上述查詢可以將emp_log作爲驅動表:

SELECT a.empno, a.ename,c.deptno, c.dname, a.log_action
  FROM emp b, dept c, emp_log a;

指定WHERE條件順序

在查詢表時,WHERE條件的順序往往影響了執行的性能。默認情況下,Oracle採用自下而上的順序解析WHERE子句,因此在處理多表查詢時,表之間的連接必須寫在其他的 WHERE條件之前,過濾數據記錄的條件寫在最後,以便過濾了數據之後再進行連接處理。
另外在索引的利用上,也需要注意,使用索引列作爲條件比非索引列作爲條件的性能要高很多。當然在面對複雜的查詢時,也許需要在多個索引之間權衡,比如考慮索引的優先級,調增索引列的順序等。

避免使用*符號

在選擇一個表中的所有列時,大多數人習慣使用*符號:SELECT * FROM emp;
Oracle在遇到*符號時,會去查詢數據字典表獲取所有的列信息,然後依次轉換成所有的列名,這將耗費一些執行時間,因此應該儘量寫明字段名,而不是直接使用*符號。

使用DECODE函數

比如統計emp表中部門編號爲20和部門編號爲30的員工的人數和薪資彙總,可以這樣寫:

SELECT COUNT(*),SUM(sal) FROM emp WHERE deptno = 20
UNION
SELECT COUNT(*),SUM(sal) FROM emp WHERE deptno =30;

但是這樣寫的話,SQL優化器對emp表進行了兩次全表掃描。通過DECODE語句,可以這樣寫:

SELECT COUNT (DECODE (deptno, 20, 'X', NULL)) dept20_count,
       COUNT (DECODE (deptno, 30, 'X', NULL)) dept30_count,
       SUM (DECODE (deptno, 20, sal, NULL)) dept20_sal,
       SUM (DECODE (deptno, 30, sal, NULL)) dept30_sal
  FROM emp
 WHERE deptno IN (20, 30);

這樣的話只對emp表進行了一次全表掃描。

使用WHERE而非HAVING

WHERE和HAVING都可以用來過濾數據,但是由於WHERE中不能使用聚集函數如COUNT、MAX、MIN、AVG、SUM等函數,但是HAVING可以使用聚集函數來過濾結果集。
爲了編寫高性能的SQL語句,必須要了解,WHERE是在GROUP BY之前篩選記錄,而HAVING是在GROUP BY之後篩選記錄,因此要儘量在GROUP BY之前將數據使用WHERE進行過濾。

使用UNION而非OR

如果考慮在SQL查詢中使用OR語句,那麼需要特別注意其性能問題。如果要進行OR運算的兩個列都是索引列,可以考慮使用UNION來提升性能。比如在emp表中,empno和ename都創建了索引,當需要在empno和ename之間進行OR操作查詢時:

SELECT empno, ename, job, sal
  FROM emp
 WHERE empno > 7500 OR ename LIKE 'S%';

可以改爲UNION提升性能:

SELECT empno, ename, job, sal
  FROM emp
 WHERE empno > 7500
UNION
SELECT empno, ename, job, sal
  FROM emp
 WHERE ename LIKE 'S%';

但是這種方式必須要確保兩個列都是索引列,否則執行的性能可能往往還不如直接用OR。

如果堅持使用OR,需要儘量將返回記錄最少的索引列寫在前面,這樣能獲取較好的性能。
另外的一個建議是在要對單個字段值進行OR的時候,可以考慮使用IN來代替。

使用EXISTS而非IN

比如要查詢位於芝加哥的所有員工列表:

SELECT *
  FROM emp
 WHERE deptno IN (SELECT deptno
                    FROM dept
                   WHERE loc = 'CHICAGO');

可以將IN改爲EXISTS來提升性能:

SELECT a.*
  FROM emp a
 WHERE EXISTS (SELECT 1
                 FROM dept b
                WHERE a.deptno = b.deptno AND loc = 'CHICAGO');

同樣的替換也發生正在NOT IN和NOT EXISTS之間。NOT IN將執行一個內部的排序和合並,實際上它對子查詢中的表執行了一次全表掃描,因此效率較低。例如要查詢emp表中員工所在部門不爲芝加哥的員工列表,如果使用NOT IN:

SELECT *
  FROM emp
 WHERE deptno NOT IN (SELECT deptno
                    FROM dept
                   WHERE loc = 'CHICAGO');     

爲了提升性能,可以考慮使用連接查詢,這是最有效率的一種辦法:

SELECT a.*
  FROM emp a, dept b
 WHERE a.deptno = b.deptno AND b.loc <> 'CHICAGO';      

但是也可以考慮NOT EXISTS:

SELECT a.*
  FROM emp a
 WHERE NOT EXISTS (SELECT 1
                     FROM dept b
                    WHERE a.deptno = b.deptno AND loc = 'CHICAGO');
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章