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表。
-
SQL> conn system/123456 as sysdba
-
-- 如果下面語句沒有執行成功,可以找到這個檔案,單獨執行這個檔案裡的建表語句
-
SQL> @/rdbms/admin/utlxplan.sql
-
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且顯示執行計劃,可以使用如下的語句:
-
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
-
2 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 (%C
-
--------------------------------------------------------------------------------
-
| 0 | SELECT STATEMENT | | 1 | 115 | 3
-
| 1 | TABLE ACCESS BY INDEX ROWID| CFREPORTDATA | 1 | 115 | 3
-
|* 2 | INDEX RANGE SCAN | PK_CFREPORTDATA | 1 | | 2
-
--------------------------------------------------------------------------------
-
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方式會根據統計資訊來產生執行計劃.
統計資訊給出表的大小,多少行,每行的長度等資訊,這些統計資訊起初在庫內是沒有的,是做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語句的效能。
-
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塊。
關於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的員工資訊
-
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
比如查詢位於芝加哥的所有員工列表可以考慮使用IN
-
SELECT *
-
FROM emp
-
WHERE deptno IN (SELECT deptno
-
FROM dept
-
WHERE loc = 'CHICAGO');
替換成exists可以獲取更好的查詢效能
-
SELECT a.*
-
FROM emp a
-
WHERE NOT 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.避免隱式型別轉換
原文來自https://blog.csdn.net/ochangwen/article/details/52728637