1.SQL語句的執行計劃
使用EXPLAIN PLAN語句來確定Oracle數據庫下指定SQL語句的執行計劃,這個語句插入每一步執行計劃的行描述到指定表中。你也可使用EXPLAIN PLAN語句作爲SQL跟蹤工具的一部分。
EXPLAIN PLAN命令的語法如下:
EXPLAIN PLAN
[ SET STATEMENT_ID = string ]
[ INTO [ schema. ] table_name [ @ dblink ] ]
FOR sql_statement ;
EXPLAIN PLAN的相關選下如下:
- STATEMENT_ID
SQL語句的唯一標識符。通過使用SQL語句的標識符,可以向一個計劃表中存入多條SQL語句。
- TABLE_NAME
存儲執行計劃的計劃表的名稱。此表必須已經存在並且與標準表結構一致。如果沒有指定計劃表名稱,EXPLAIN PLAN會嘗試使用表名PLAN_TABLE.
-
SQL_STATEMENT
你想要知道其執行計劃的那條SQL語句。這條SQL語句必須是有效的。並且你也必須有足夠的權限來執行它。這條SQL語句可以含有綁定變量。
2.計劃表
默認情況下,Oracle會將執行計劃插入如到一張名爲PLAN_TABLE的表中。可以使用腳本utlexplain.sql來創建自己的計劃表。這個腳本位於Oracle軟件安裝目錄的子目錄$ORACLE_HMOE/rmdbs/admin/中。然而,從Oracle 10g開始,Oracle會創建一個全局臨時表PLAN_TABLE供所有用戶使用,所以通常情況下不需要創建自己的計劃表。由於此默認的計劃表是一個全局臨時表,所以你無法看到其他會話插入的執行計劃,你的執行計劃也會隨着自己會話的結束而自動消失。
(計劃表)
列名 |
類型 |
描述 |
---|---|---|
STATEMENT_ID |
VARCHAR2(30) |
在EXPLAIN PLAN的SET STATEMENT_ID子句提供的SQL語句的唯一標誌符。 |
PLAN_ID |
NUMBER |
執行計劃的在全局表plan_table中的唯一標識符 |
TIMESTAMP |
DATE |
EXPLAN PLAN語句執行的日期和時間 |
REMARKS |
VARCHAR2(80) |
註釋 |
OPERATION |
VARCHAR2(30) |
執行的操作類型。如TABLE ACCESS,SORT或HASH JOIN |
OPTIONS |
VARCHAR2(225) |
操作的附加信息,例如,以TABLE SCAN爲例,選項可能是FULL或BY ROWID |
OBJECT_NODE |
VARCHAR2(128) |
如果是分佈式查詢,這一列表示用於引用對象的數據庫鏈接名稱。如果並行查詢,它的值可能對應一個臨時的結果集。 |
OBJECT_OWNER |
VARCHAR2(30) |
對象的名字 |
OBJECT_NAME |
VARCHAR2(30) |
對象名稱 |
OBJECT_ALIAS |
VARCHAR2(65) |
對象的別名 |
OBJECT_INSTANCE |
NUMERIC |
對象在SQL語句中的位置 |
OBJECT_TYPE |
VARCHAR2(30) |
對象的類型(表,索引等) |
OPTIMIZER |
VARCHAR2(255) |
解釋SQL語句時生效的優化器 |
SEARCH_COLUMNS |
NUMBERIC |
未使用 |
ID |
NUMERIC |
執行計劃的ID號 |
PARENT_ID |
NUMERIC |
上一個步驟的ID號 |
DEPTH |
NUMERIC |
操作的深度 |
POSITION |
NUMERIC |
如果兩個步驟有相同的父步驟,有更低POSITION值的步驟將被先執行 |
COST |
NUMERIC |
優化器估算出來的此操作的相對成本 |
CARDINALITY |
NUMERIC |
優化器預期這一步將飯後的記錄數 |
BYTES |
NUMERIC |
預計這一步將返回的字節數 |
OTHER_TAG |
VARCHAR2(255) |
標識OTHER列中的值的類型。 |
PARTITION_START |
VARCHAR2(255) |
訪問的分區範圍的起始分區 |
PARTITION_STOP |
VARCHAR2(255) |
訪問的分區範圍的結束分區 |
PARTITION_ID |
NUMERIC |
計算PARTITION_START和PARTITION_STOP列的值對的ID |
OTHER |
LONG |
對於分佈式查詢,這列可能是包含發往遠程數據庫的SQL語句的文本。對於並行查詢,它比啊是並行從屬進程執行的SQL語句。 |
DISTRIBUTION |
VARCHAR2(30) |
描述記錄是如何從一組並行查詢從屬進程分配到後續的“消費者”從屬進程的。 |
CPU_COST |
NUMERIC |
估算出來的操作的CPU成本 |
IO_COST |
NUMERIC |
估算出來的的操作的IO成本 |
TEMP_SPACE |
NUMERIC |
估算出來的這一步操作所使用的臨時存儲的空間大小 |
ACCESS_PREDICATES |
VARCHAR2(4000) |
SQL語句中,確定如何在當前步驟中提取記錄的子句。 |
FILTER_PREDICATES |
VARCHAR2(4000) |
SQL語句中確定對見記錄進行過濾的子句路,如WHERE子句在非索引列上的條件。 |
PROJECTION |
VARCHAR2(4000) |
決定將返回的記錄的子句,通常是SELECT後面的字段列表 |
TIME |
NUMBER(20,2) |
優化器爲這一步執行估算的時間消耗 |
QBLOCK_NAME |
VARCHAR2(30) |
查詢塊的唯一標識符。 |
(常見的執行計劃操作)
操 作 |
選 項 |
描 述 |
---|---|---|
表的訪問路徑 |
TABLE ACCESS
FULL
全表掃描,他會讀取表中的每一條記錄(嚴格地說,它讀取表的高水位以內的每個數據塊)
CLUSTER
通過索引簇的鍵來訪問數據
HASH
通過散鍵列來訪問表中匹配特定的散列值的一條或多條記錄
BY INDEX ROWID
通過指定ROWID來訪問表中的單條記錄。ROWID是訪問單條記錄的最快捷的方式。通常,ROWID的信息都是有一個相關的索引檢索而來
BY USER ROWID
通過提供一個綁定變量、字面變量或WHERE CURRENT OF CURSOR子句來通過ROWID進行訪問
BY GLOBAL INDEX ROWID
通過由全局分區索引獲得的ROWID進行訪問
BY LOCAL INDEX ROWID
通過本地分區索獲得的ROWID進行訪問
SAMPLE
使用SAMPLE子句得到結果集的一個經過採樣的子集
EXTERNAL TABLE ACCESS
訪問一張外部表
RESULT CACHE
這個SQL結果集可能來自結果集緩存
MAT_VIEW REWIRTE ACCESS
SQL語句被重寫以利用物化視圖
索引操作
ADN_EQUAL
合併來自一個或多個索引掃描的結果集
INDEX
UNIQUE SCAN
只返回一條記錄的地址(ROWID)的索引檢索
RANGE SCAN
返回多條記錄的ROWID的索引檢索。之所以可以這樣返回,是因爲是非唯一索引或是使用了區間操作符(例如,>)
FULL SCAN
按照索引的順序掃描整個索引
KIP SCAN
搜索碎索引鍵中哦非前導列的索引掃描
FULL SCAN(MAX/MIN)
檢索最高或最低的索引條目
FAST FULL SCAN
按照塊順序掃描索引的每個條目,可能會使用多塊讀取
DOMAIN INDEX
域索引(用戶定義的索引類型)檢索
位圖操作
BITMAP
CONVERSION
將ROWID轉換成位圖或將位圖轉換成ROWID
INDEX
從位圖中提取一個值或一個範圍的值
MERGE
合併多個位圖
MINUS
從一個位圖中減去另一個位圖
OR
按位(bit-wise)對兩個位圖做OR操作
表連接
CONNECT BY
對前一個步驟的輸出結果執行一個層次化的自聯接操作
MERGE JOIN
對前一個步驟的輸出結果執行一次合併聯接
NESTED LOOPS
對前一個步驟執行嵌套循環聯接。對於上層的結果集中的每一行,都會掃描下層的結果集以找到匹配的記錄
HASH JOIN
對兩個記錄源(row source)進行散列聯接
任何連接操作
OUTER
此連接爲外聯接
任何連接操作
ANTI
此連接爲反聯接
任何連接操作
SEMI
此連接爲半聯接
任何連接操作
CARTESIAN
一個結果集中的每一條記錄與另一個結果中的每一條記錄進行聯接
集合操作
CONCATENATION
與顯示指定一個UNION語句一樣,多個結果集被按照同樣的方式做合併。它通常會發生在對索引列使用OR語句時
INTERSECTION
對兩個結果集進行比較,只返回在兩個結果集中都存在的記錄。通常只有顯式地使用INTERSECT子句,這個操作纔會發生
MINUS
除在第二個結果中出現過的記錄外,返回一個結果中的所有記錄。它是使用MINUS集合操作符的結果
UNION-ALL
對兩個結果集進行合併,並返回兩個結果集中的所有記錄
UNION
對兩個結果集進行合併,並返回兩個結果集中的所有記錄,但是不返回重複記錄
VIEW
要麼訪問一個視圖定義,要麼創建一個臨時表來存儲結果集
其他雜項
FOR UPDATE
由於FOR UPDATE子句的原因,返回的記錄都會被鎖住
COLLECTION ITERATOR
各種
從一個表函數提取記錄的操作(也就是 FROM TABLE())
FAST DUAL
訪問DUAL表,以避免從緩衝區高數緩存中讀取
FILTER
從結果集中排除掉不匹配給定選取條件的記錄
REMOTE
通過數據庫鏈接訪問一個外部的數據庫
FIRST ROW
獲取查詢的第一條記錄
SEQUENCE
使用Oracle序列號生成器來獲得一個唯一的序列號
INLIST ITERATOR
對於IN列表中的每個值都執行一次下一個操作
LOAD AS SELECT
表示這是一個基於SELECT語句的直接路徑INSERT操作
FIXED TABLE
訪問固定的(X$或V$)表
FIXED INDEX
訪問固定表X$上的索引
WINDOW
BUFFER
支持分析函數(如OVER())的內部操作
WINDOW
SORT [PUSHED]RANK
分析函數需要爲實現RANK()函數執行一次排序操作
分區操作
PARTITION
SINGLE
訪問單個分區
ITERATOR
訪問多個分區
ALL
訪問所有分區
INLIST
基於IN列表中的值來訪問多個分區
彙總操作
COUNT
爲了滿足COUNT()函數而計算結果集中的記錄數
COUNT
STOPKEY
計算結果集中的記錄數,當達到一定數量後就停止處理。這通常發生在使用了WHERE子句,並指定了一個最大值ROWNUM(例如,WHERE ROWNUM<=10)的情況下
BUFFER
SORT
對臨時結果集做的一次內存排序
HASH
GROUP BY
使用散列操作而不是排序操作實現GROUP BY
INLIST
ITERATOR
對於IN列表中的每個值都實現一次子操作
SORT
ORDER BY
爲了滿足ORDER BY子句而對結果集進行排序
AGGREGATE
當在已經分好組的數據上使用分組函數是會出現此操作
JOIN
爲了準備合併連接而對記錄進行排序
UNIQUE
排除重複記錄的排序操作,通常是使用DISTINCT子句的結果
GROUP BY
爲GROUP BY子句對結果集進行排序分組
GROUP BY NOSORT
不需要進行排序操作的GROUP BY操作
GROUP BY ROLLUP
含有ROLLUP選項的GROUP BY操作
GROUP BY CUBE
含有CUBE選項的GROUP BY操作
3.查看執行計劃
當SQL語句的執行計劃生成以後,我們就可以去查看SQL語句的執行計劃了。有兩種方法可以查看執行計劃:直接查看計劃表和DBMS_XPLAN.DISPALY表函數。
第一種方法:
爲了更好地理解計劃表中的數據,需要針對計劃表做層次查詢。通過SELECT語句的 CONNECT BY子句對PARENT_ID和ID兩列進行自連接。這種查詢語句通常的寫法如下:
select rtrim(lpad(' ', 2 * level) || rtrim(operation) || ' ' || rtrim(options)) description,
object_owner,
object_name,
cost,
cardinality,
bytes,
io_cost,
cpu_cost
from plan_table
connect by prior id = parent_id
start with id = 0
第二種方法:
與手工查詢計劃表相比,使用DBMS_XPLAN通常可以更好的結果,它的語法更加簡單,還提供了多種有用的輸出格式,並且可以利用緩存的執行計劃統計信息。
調用DBMS_XPLAN函數最簡單的方法就是使用 select * from table()語句,如下面的語句:
select * from table(dbms_xplan.function(options));
最常用的兩個DBMS_XPLAN函數:
DBMS_XPLAN.DISPLAY(
table_name IN VARCHAR2 DEFAULT 'PLAN_TABLE',
statement_id IN VARCHAR2 DEFAULT NULL,
format IN VARCHAR2 DEFAULT 'TYPICAL',
filter_preds IN VARCHAR2 DEFAULT NULL);
DBMS_XPLAN.DISPLAY_CURSOR(
sql_id IN VARCHAR2 DEFAULT NULL,
child_number IN NUMBER DEFAULT NULL,
format IN VARCHAR2 DEFAULT 'TYPICAL');
4.Examples
創建emp_test表
create table emp_test as select *from emp;
create unique index EMP_TEST_U1 on EMP_TEST (empno);
create index emp_test_n1 on EMP_TEST (ename);
通過EXPLAIN PLAN語句,插入指定SQL語句的執行計劃。
SQL> explain plan set statement_id ='plan_sql_id' for select * from emp_test t where t.ename='SCOTT';
Explained
手動查詢計劃表查看計劃:
SQL> select rtrim(lpad(' ', 2 * level) || rtrim(operation) || ' ' || rtrim(options)) description,
2 object_owner,
3 object_name,
4 cost,
5 cardinality,
6 bytes,
7 io_cost,
8 cpu_cost
9 from plan_table
10 connect by prior id = parent_id
11 start with id = 0;
DESCRIPTION OBJECT_OWNER OBJECT_NAME COST CARDINALITY BYTES IO_COST CPU_COST
-------------------------------- ------------- ------------- ------ ----------- ----- ------- ---------
SELECT STATEMENT 2 1 38 2 14733
TABLE ACCESS BY INDEX ROWID SCOTT EMP_TEST 2 1 38 2 14733
INDEX RANGE SCAN SCOTT EMP_TEST_N1 1 1 1 7321
調用DBMS_XPLAN函數查看:
SQL> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1758671844
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 2 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID| EMP_TEST | 1 | 38 | 2 (0)|
|* 2 | INDEX RANGE SCAN | EMP_TEST_N1 | 1 | | 1 (0)|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("T"."ENAME"='SCOTT')