1. 程式人生 > >PLSQL效能優化技巧

PLSQL效能優化技巧

1、理解執行計劃

1-1.什麼是執行計劃
    oracle資料庫在執行sql語句時,oracle的優化器會根據一定的規則確定sql語句的執行路徑,以確保sql語句能以最優效能執行.在oracle資料庫系統中為了執行sql語句,oracle可能需要實現多個步驟,這些步驟中的每一步可能是從資料庫中物理檢索資料行,或者用某種方法準備資料行,讓編寫sql語句的使用者使用,oracle用來執行語句的這些步驟的組合被稱為執行計劃
  當執行一個sql語句時oracle經過了4個步驟:
  ①.解析sql語句:主要在共享池中查詢相同的sql語句,檢查安全性和sql語法與語義。
  ②.建立執行計劃及執行:包括建立sql語句的執行計劃及對錶資料的實際獲取。
  ③.顯示結果集:對欄位資料執行所有必要的排序,轉換和重新格式化。
  ④.轉換欄位資料:對已通過內建函式進行轉換的欄位進行重新格式化處理和轉換.

1-2.檢視執行計劃
    檢視sql語句的執行計劃,比如一些第三方工具  需要先執行utlxplan.sql指令碼建立explain_plan表。

 

 

 
  1. SQL> conn system/123456 as sysdba

  2. -- 如果下面語句沒有執行成功,可以找到這個檔案,單獨執行這個檔案裡的建表語句

  3. SQL> @/rdbms/admin/utlxplan.sql

  4. SQL> grant all on sys.plan_table to public;

  在建立表後,在SQL*Plus中就可以使用set autotrace語句來顯示執行計劃及統計資訊。常用的語句與作用如下:
set autotrace on explain:執行sql,且僅顯示執行計劃
set autotrace on statistics:執行sql 且僅顯示執行統計資訊
set autotrace on :執行sql,且顯示執行計劃與統計資訊,無執行結果
set autotrace traceonly:僅顯示執行計劃與統計資訊,無執行結果
set autotrace off:關閉跟蹤顯示計劃與統計
  比如要執行SQL且顯示執行計劃,可以使用如下的語句:

 
  1. SQL> set autotrace on explain

  2. SQL> col ename format a20;

  3. SQL> select empno,ename from emp where empno=7369;

上面不一定可以執行成功,使用這個:explain plan for sql語句

 

 

 
  1. SQL> explain plan for

  2. 2 select * from cfreportdata where outitemcode='CR04_00160' and quarter='1' and month='2015';

  3. Explained

  4.  
  5. SQL> select * from table(dbms_xplan.display);

  6. PLAN_TABLE_OUTPUT

  7. --------------------------------------------------------------------------------

  8. Plan hash value: 3825643284

  9. --------------------------------------------------------------------------------

  10. | Id | Operation | Name | Rows | Bytes | Cost (%C

  11. --------------------------------------------------------------------------------

  12. | 0 | SELECT STATEMENT | | 1 | 115 | 3

  13. | 1 | TABLE ACCESS BY INDEX ROWID| CFREPORTDATA | 1 | 115 | 3

  14. |* 2 | INDEX RANGE SCAN | PK_CFREPORTDATA | 1 | | 2

  15. --------------------------------------------------------------------------------

  16. Predicate Information (identified by operation id):

  17. ---------------------------------------------------

  18. 2 - access("OUTITEMCODE"='CR04_00160' AND "MONTH"='2015' AND "QUARTER"='1')

  19. filter("MONTH"='2015' AND "QUARTER"='1')

  20. 15 rows selected

 

    PL/SQL DEVELOPER提供了一個執行計劃視窗,如果在SQL Windows的視窗,按F8是執行該sql,按f5會顯示該sql的執行計劃。

3.理解執行計劃
    1.全表掃描(full table scans):這種方式會讀取表中的每一條記錄,順序地讀取每一個數據塊直到結尾標誌,對於一個大的資料表來說,使用全表掃描會降低效能,但有些時候,比如查詢的結果佔全表的資料量的比例比較高時,全表掃描相對於索引選擇又是一種較好的辦法。
    2.通過ROWID值獲取(table access by rowid):行的rowid指出了該行所在的資料檔案,資料塊及行在該塊中的位置,所以通過rowid來存取資料可以快速定位到目標資料上,是oracle存取單行資料的最快方法。
    3.索引掃描(index scan):先通過索引找到物件的rowid值,然後通過rowid值直接從表中找到具體的資料,能大大提高查詢的效率。

2.連線查詢的表順序

    預設情況下,優化器會使用all_rows優化方式,也就是基於成本的優化器CBO生成執行計劃,CBO方式會根據統計資訊來產生執行計劃.
    統計資訊給出表的大小,多少行,每行的長度等資訊,這些統計資訊起初在庫內是沒有的,是做analyee後才發現的,很多時候過期統計資訊會令優化器做出一個錯誤的執行計劃,因此應及時更新這些資訊。
    在CBO模式下,當對多個表進行連線查詢時,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個以上的表連線查詢,就需要選擇交叉表作為基礎表。交叉表是指那個被其他表所引用的表,由於emp_log是dept與emp表中的交叉表,既包含dept的內容又包含emp的內容。

select a.empno,a.ename,c.deptno,c.dname,a.log_action from emp b,dept c,emp_log a;

 

3.指定where條件順序

    在查詢表時,where子句中條件的順序往往影響了執行的效能。預設情況下,oracle採用自下而上的順序解析where子句,因此在處理多表查詢時,表之間的連線必須寫在其他的where條件之前,但是過濾資料記錄的條件則必須寫在where子句的尾部,以便在過濾了資料之後再進行連線處理,這樣可以提升sql語句的效能。

 
  1. SELECT a.empno, a.ename, c.deptno, c.dname, a.log_action, b.sal

  2. FROM emp b, dept c, emp_log a

  3. WHERE a.deptno = b.deptno AND a.empno=b.empno AND c.deptno IN (20, 30)

    從SQL執行計劃中可以看到const成本值為10。如果使用如下不好的查詢方式,const成本值為32

 
  1. SELECT a.empno, a.ename, c.deptno, c.dname, a.log_action, a.mgr

  2. FROM emp b, dept c, emp_log a

  3. WHERE c.deptno IN (20, 30) AND a.deptno = b.deptno

 

4.避免使用*符號

  有時我們習慣使用*符號,如

 SELECT * FROM emp

    Oracle在遇到*符號時,會去查詢資料字典表中獲取所有的列資訊,然後依次轉換成所有的列名,這將耗費較長的執行時間,因此儘量避免使用*符號獲取所有的列資訊

 

5.使用decode函式

    是Oracle才具有的一個功能強大的函式
    比如統計emp表中部門編號為20和部門編號為30的員工的人數和薪資彙總,如果不使用decode那麼就必須用兩條sql語句

 

 
  1. select count(*),SUM(sal) from emp where deptno=20;

  2. union

  3. select count(*),SUM(sal) from emp where deptno=30;

  通過Union將兩條SQL語句進行合併,實際上通過執行計劃可以看到,SQL優化器對emp進行了兩次全表掃描
通過decode語句,可以再一個sql查詢中獲取到相同的結果,並且將兩行結果顯示為單行。

 

 

 
  1. SELECT COUNT (DECODE (deptno, 20, 'X', NULL)) dept20_count,

  2. COUNT (DECODE (deptno, 30, 'X', NULL)) dept30_count,

  3. SUM (DECODE (deptno, 20, sal, NULL)) dept20_sal,

  4. SUM (DECODE (deptno, 30, sal, NULL)) dept30_sal

  5. FROM emp;

    通過靈活的運用decode函式,可以得到很多意想不到的結果,比如在group by 或order by子句中使用decode函式,或者在decode塊中巢狀另一個decode塊。

 

關於decode函式詳解:http://blog.csdn.net/ochangwen/article/details/52733273

6.使用where而非having

    where子句和having子句都可以過濾資料,但是where子句不能使用聚集函式,如count max min avg sum等函式。因此通常將Having子句與Group By子句一起使用
    注意:當利用Group By進行分組時,可以沒有Having子句。但Having出現時,一定會有Group By
  需要了解的是,WHERE語句是在GROUP BY語句之前篩選出記錄,而HAVING是在各種記錄都篩選之後再進行過濾。也就是說HAVING子句是在從資料庫中提取資料之後進行篩選的,因此在編寫SQL語句時,儘量在篩選之前將資料使用WHERE子句進行過濾,因此執行的順序應該總是這樣。
  ①.使用WHERE子句查詢符合條件的資料
  ②.使用GROUP BY子句對資料進行分組。
  ③.在GROUP BY分組的基礎上執行聚合函式計算每一組的值
  ④.用HAVING子句去掉不符合條件的組。
例子:查詢部門20和30的員工薪資總數大於1000的員工資訊

 

 
  1. select empno,deptno,sum(sal)

  2. from emp group by empno,deptno

  3. having sum(sal) > 1000 and deptno in (20,30);

    在having子句中,過濾出部門編號為20或30的記錄,實際上這將導致查詢取出所有部門的員工記錄,在進行分組計算,最後才根據分組的結果過濾出部門 20和30的記錄。這非常低效,好的演算法是先使用where子句取出部門編號為20和30的記錄,再進行過濾。修改如下:

 
  1. select empno,deptno,sum(sal)

  2. from emp where deptno in (20,30)

  3. group by empno,deptno having sum (sal) > 1000;

 

7.使用UNION而非OR

  如果要進行OR運算的兩個列都是索引列,可以考慮使用union來提升效能。
  例子:比如emp表中,empno和ename都建立了索引列,當需要在empno和ename之間進行OR操作查詢時,可以考慮將這兩個查詢更改為union來提升效能。

select empno,ename,job,sal from emp where empno > 7500 OR ename LIKE 'S%';

使用UNION

 
  1. select empno,ename,job,sal from emp where empno > 7500

  2. UNION

  3. select empno,ename,job,sal from emp where ename LIKE 'S%';

    但這種方式要確保兩個列都是索引列。否則還不如OR語句。
    如果堅持使用OR語句,①.需要記住儘量將返回記錄最少的索引列寫在最前面,這樣能獲得較好的效能,例如empno > 7500 返回的記錄要少於對ename的查詢,因此在OR語句中將其放到前面能獲得較好的效能。②.另外一個建議是在要對單個欄位值進行OR計算的時候,可以考慮使用IN來代替

select empno,ename,job,sal from emp where deptno=20 OR deptno=30;

上面的SQL如果修改為使用In,效能更好

 

8.使用exists而非IN

    比如查詢位於芝加哥的所有員工列表可以考慮使用IN

 
  1. SELECT *

  2. FROM emp

  3. WHERE deptno IN (SELECT deptno

  4. FROM dept

  5. WHERE loc = 'CHICAGO');

替換成exists可以獲取更好的查詢效能

 
  1. SELECT a.*

  2. FROM emp a

  3. WHERE NOT EXISTS (SELECT 1

  4. FROM dept b

  5. WHERE a.deptno = b.deptno AND loc = 'CHICAGO');

    同樣的替換頁發生在not in 和not exists之間,not in 子句將執行一個內部的排序和合並,實際上它對子查詢中的表執行了一次全表掃描,因此效率低,在需要使用NOT IN的場合,英愛總是考慮把它更改成外連線或NOT EXISTS

 
  1. SELECT *

  2. FROM emp

  3. WHERE deptno NOT IN (SELECT deptno

  4. FROM dept

  5. WHERE loc = 'CHICAGO');

為了提高較好的效能,可以使用連線查詢,這是最有效率的的一種辦法

 
  1. SELECT a.*

  2. FROM emp a, dept b

  3. WHERE a.deptno = b.deptno AND b.loc <> 'CHICAGO';

也可以考慮使用NOT EXIST

 
  1. select a.* from emp a

  2. where NOT EXISTS (

  3. select 1 from dept b where a.deptno =b.deptno and loc='CHICAGO');

9.避免低效的PL/SQL流程控制語句
    PLSQL在處理邏輯表示式值的時候,使用的是短路徑的計算方式。

 
  1. DECLARE

  2. v_sal NUMBER := &sal; --使用繫結變數輸入薪資值

  3. v_job VARCHAR2 (20) := &job; --使用繫結變數輸入job值

  4. BEGIN

  5. IF (v_sal > 5000) OR (v_job = '銷售') --判斷執行條件

  6. THEN

  7. DBMS_OUTPUT.put_line ('符合匹配的OR條件');

  8. END IF;

  9. END;

    首先對第一個條件進行判斷,如果v_sal大於5000,就不會再對v_job條件進行判斷,靈活的運用這種短路計算方式可以提升效能。應該總是將開銷較低的判斷語句放在前面,這樣當前面的判斷失敗時,就不會再執行後面的具有較高開銷的語句,能提升PL/SQL應用程式的效能.
    舉個例子,對於and邏輯運算子來說,只有左右兩邊的運算為真,結果才為真。如果前面的結果第一個運算時false值,就不會進行第二個運算、

 
  1. DECLARE

  2. v_sal NUMBER := &sal; --使用繫結變數輸入薪資值

  3. v_job VARCHAR2 (20) := &job; --使用繫結變數輸入job值

  4. BEGIN

  5. IF (Check_Sal(v_sal) > 5000) AND (v_job = '銷售') --判斷執行條件

  6. THEN

  7. DBMS_OUTPUT.put_line ('符合匹配的AND條件');

  8. END IF;

  9. END;

    這段程式碼有一個性能隱患,check_sal涉及一些業務邏輯的檢查,如果讓check_sal函式的呼叫放在前面,這個函式總是被呼叫,因此處於效能方面的考慮,應該總是將v_job的判斷放到and語句的前面.

 
  1. DECLARE

  2. v_sal NUMBER := &sal; --使用繫結變數輸入薪資值

  3. v_job VARCHAR2 (20) := &job; --使用繫結變數輸入job值

  4. BEGIN

  5. IF (v_job = '銷售') AND (Check_Sal(v_sal) > 5000) --判斷執行條件

  6. THEN

  7. DBMS_OUTPUT.put_line ('符合匹配的AND條件');

  8. END IF;

  9. END;

 

10.避免隱式型別轉換

 

 

原文來自https://blog.csdn.net/ochangwen/article/details/52728637