PLSQL性能優化技巧

文章轉自 :http://www.javashuo.com/article/p-otrzlgrm-mu.htmlhtml

案例引用:https://blog.csdn.net/weixin_39452806/article/details/79045946  算法

好文要頂,感謝博主們分享!sql

1、理解執行計劃

1.什麼是執行計劃
            oracle數據庫在執行sql語句時,oracle的優化器會根據必定的規則肯定sql語句的執行路徑,以確保sql語句能以最優性能執行。在oracle數據庫系統中爲了執行sql語句,oracle可能須要實現多個步驟,這些步驟中的每一步多是從數據庫中物理檢索數據行,或者用某種方法準備數據行,讓編寫sql語句的用戶使用,oracle用來執行語句的這些步驟的組合被稱爲執行計劃數據庫

當執行一個sql語句時oracle通過了4個步驟:
  ①.解析sql語句:主要在共享池中查詢相同的sql語句,檢查安全性和sql語法與語義。
  ②.建立執行計劃及執行:包括建立sql語句的執行計劃及對錶數據的實際獲取。
  ③.顯示結果集:對字段數據執行全部必要的排序,轉換和從新格式化。
  ④.轉換字段數據:對已經過內置函數進行轉換的字段進行從新格式化處理和轉換.安全

2.查看執行計劃
           查看sql語句的執行計劃,好比一些第三方工具  須要先執行utlxplan.sql腳本建立explain_plan表oracle

SQL> conn system/123456 as sysdba;
-- 若是下面語句沒有執行成功,能夠找到這個文件,單獨執行這個文件裏的建表語句
SQL> @/rdbms/admin/utlxplan.sql;
SQL> grant all on sys.plan_table to public;

在建立表後,在SQL*Plus中就可使用set autotrace語句來顯示執行計劃及統計信息。經常使用的語句與做用以下:ide

set autotrace on explain:執行sql,且僅顯示執行計劃
set autotrace on statistics:執行sql 且僅顯示執行統計信息
set autotrace on :執行sql,且顯示執行計劃與統計信息,無執行結果
set autotrace traceonly:僅顯示執行計劃與統計信息,無執行結果
set autotrace off:關閉跟蹤顯示計劃與統計

好比要執行SQL且顯示執行計劃,可使用以下的語句:函數

SQL> set autotrace on explain;
SQL> col ename format a20;
SQL> select empno,ename from emp where empno=7369;

上面不必定能夠執行成功,使用這個:explain plan for sql語句工具

SQL> explain plan for select * from cfreportdata where outitemcode='CR04_00160' and quarter='1' and month='2015';

Explained

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

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3825643284

--------------------------------------------------------------------------------

| Id | Operation                   | Name            | Rows | Bytes | Cost (%CPU)

--------------------------------------------------------------------------------

| 0  | SELECT STATEMENT            |                 | 1    | 115   | 3(0)

| 1  | TABLE ACCESS BY INDEX ROWID | CFREPORTDATA    | 1    | 115   | 3(0)

|* 2 | INDEX RANGE SCAN            | PK_CFREPORTDATA | 1    |       | 2(0)

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

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

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

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方式會根據統計信息來產生執行計劃。
    統計信息給出表的大小,多少行,每行的長度等信息,這些統計信息起初在庫內是沒有的,是作analyzee後才發現的,不少時候過時統計信息會令優化器作出一個錯誤的執行計劃,所以應及時更新這些信息。
    在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語句的性能。

SELECT a.empno, a.ename, c.deptno, c.dname, a.log_action, b.sal
FROM emp b, dept c, emp_log a
WHERE a.deptno = b.deptno AND a.empno=b.empno AND c.deptno IN (20, 30)
從SQL執行計劃中能夠看到const成本值爲10。若是使用以下很差的查詢方式,const成本值爲32
SELECT a.empno, a.ename, c.deptno, c.dname, a.log_action, a.mgr
FROM emp b, dept c, emp_log a
WHERE c.deptno IN (20, 30) AND  a.deptno = b.deptno

4、避免使用  *  符號

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

SELECT * FROM emp

    Oracle在遇到*符號時,會去查詢數據字典表中獲取全部的列信息,而後依次轉換成全部的列名,這將耗費較長的執行時間,所以儘可能避免使用*符號獲取全部的列信息

 

5、使用decode函數

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

select count(*),SUM(sal) from emp where deptno=20
union
select count(*),SUM(sal) from emp where deptno=30;

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

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;

    經過靈活的運用decode函數,能夠獲得不少意想不到的結果,好比在group by 或order by子句中使用decode函數,或者在decode塊中嵌套另外一個decode塊。

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的員工信息

select empno,deptno,sum(sal) 
from emp group by empno,deptno
having sum(sal) > 1000 and deptno in (20,30);

在having子句中,過濾出部門編號爲20或30的記錄,實際上這將致使查詢取出全部部門的員工記錄,在進行分組計算,最後才根據分組的結果過濾出部門 20和30的記錄,這很是低效

好的算法是先使用where子句取出部門編號爲20和30的記錄,再進行過濾。修改以下:

select empno,deptno,sum(sal) 
from emp where deptno in (20,30)
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

select empno,ename,job,sal from emp where empno > 7500
UNION
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

    好比查詢位於 'CHICAGO'  的全部員工列表能夠考慮使用in

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

    對於內表和外表一樣數量級來講exists和in的效率差異不是很大。但對於內表特別大的sql,咱們用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 子句將執行一個內部的排序和合並,實際上它對子查詢中的表執行了一次全表掃描,所以效率低,在須要使用NOT IN的場合,於是老是考慮把它更改爲外鏈接或NOT EXISTS。

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 EXIST

SELECT a.* 
FROM emp a
WHERE not exists (select 1 from dept b where a.deptno =b.deptno and loc='CHICAGO');

9、避免低效的PL/SQL流程控制語句

   PLSQL在處理邏輯表達式值的時候,使用的是短路的計算方式。

DECLARE
v_sal NUMBER := &sal; --使用綁定變量輸入薪資值
v_job VARCHAR2 (20) := &job; --使用綁定變量輸入job值
BEGIN
IF (v_sal > 5000) OR (v_job = '銷售') --判斷執行條件
THEN
DBMS_OUTPUT.put_line ('符合匹配的OR條件');
END IF;
END;

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

DECLARE
   v_sal   NUMBER        := &sal;                 --使用綁定變量輸入薪資值
   v_job   VARCHAR2 (20) := &job;                 --使用綁定變量輸入job值
BEGIN
   IF (check_sal(v_sal) > 5000) AND (v_job = '銷售')          --判斷執行條件
   THEN
      DBMS_OUTPUT.put_line ('符合匹配的AND條件');
   END IF;
END;

這段代碼有一個性能隱患,check_sal涉及一些業務邏輯的檢查,若是讓check_sal函數的調用放在前面,這個函數老是被調用,所以處於性能方面的考慮,應該老是將v_job的判斷放到and語句的前面。

DECLARE
   v_sal   NUMBER        := &sal;                     --使用綁定變量輸入薪資值
   v_job   VARCHAR2 (20) := &job;                     --使用綁定變量輸入job值
BEGIN
   IF (v_job = '銷售') AND (check_sal(v_sal) > 5000)  --判斷執行條件
   THEN
      DBMS_OUTPUT.put_line ('符合匹配的AND條件');
   END IF;
END;

10、避免隱式類型轉換

當比較不一樣數據類型的數據時, ORACLE自動對列進行簡單的類型轉換。例如 :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會優先轉換數值類型到字符類型。

咱們再看一個實際的案例,以下圖:

咱們看到SQL執行時,第二個表是全表掃描,沒有走索引。查看索引字段的類型:

咱們發現兩個表的userid 字段類型不同:一個 number,一個 varchar2, 發生了隱式轉換。 這就是當咱們sql 字段類型不同時會使用隱式和轉換

優化方案很簡單,咱們把兩個表的字段設置成同一個類型就行了。

設置成一樣的類型後就都走索引了 ,當數據量特別大時,咱們sql得優點纔會顯現出來了。