官术网_书友最值得收藏!

3.1 概述

3.1.1 什么是執行計劃

數據庫執行SQL語句是按照一定順序、分步驟完成的。至于采用怎樣的順序、用什么方法訪問數據,是由優化器來決定的。一旦優化器確定好了一個它認為最高效的執行方法,這一系列的順序、步驟就被稱為執行計劃。簡言之,Oracle用來執行目標SQL語句的這些步驟的組合就被稱為執行計劃。

下面通過一個示例說明SQL語句如何通過多個步驟處理得到需要的結果集。


select e.ename,d.dname from emp e,dept d where e.deptno=d.deptno and e.empno=7900;
--------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |     2   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |     2   (0)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| EMP     |     1 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | PK_EMP  |     1 |     0   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     4 |     1   (0)| 00:00:01 |
|*  5 |    INDEX UNIQUE SCAN         | PK_DEPT |     1 |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("E"."EMPNO"=7900)
   5 - access("E"."DEPTNO"="D"."DEPTNO")

1.訪問步驟

①Id=0:操作為SELECT STATEMENT。這一行實際表示語句的類型是一條SELECT語句,而非一個真正的操作。因此在一些執行計劃顯示當中,沒有顯示ID=0的操作。

②Id=1:操作為NESTED LOOPS,表明以嵌套循環的方式進行表間關聯。

③Id=2:操作為TABLE ACCESS BY INDEX ROWID,而索引上的ROWID則是通過子步驟(ID=3)來獲取的。

④Id=3:操作為INDEX UNIQUE SCAN,表明對索引進行唯一鍵值的訪問以獲取父操作所需要的ROWID。前面帶有*號,說明這個操作有相關的謂詞條件(訪問條件或過濾條件),后面會有詳細說明。

⑤Id=4:操作為TABLE ACCESS BY INDEX ROWID,而索引上的ROWID則是通過子步驟(ID=5)來獲取的。

⑥Id=5:操作為INDEX UNIQUE SCAN,表明對索引進行唯一鍵值的訪問以獲取父操作所需要的ROWID。前面也帶有*號。

2.謂詞條件

1)3-access("E"."EMPNO"=7900):這是操作ID=3的謂詞條件,其中access是訪問條件,表示通過指定條件定位到了數據。

2)5-access("E"."DEPTNO"="D"."DEPTNO"):這是操作ID=5的謂詞條件,access同樣是訪問條件,與前面不同的是,這里的條件值不是直接指定的,而是由嵌套循環傳入的。

3.執行過程

上面語句的執行是由多個步驟組成的,具體的步驟這里就不詳細介紹了,下面僅描述一下整體執行過程。

①首先根據指定的EMPNO=7900的條件,通過索引PK_EMP進行讀取。

②根據讀取到的索引數據中的ROWID信息,返回查詢EMP表,獲得其他需要的字段。

③按照上面的方式循環讀取整個EMP表,對于獲得的每條記錄進入內層循環。

④內層循環中,根據傳入的DEPTNO的條件,通過索引PK_DEPT進行讀取。

⑤根據讀取到的索引數據的ROWID信息,返回查詢DEPT表,獲得需要的字段,然后返回上層循環。

⑥整體循環結束后,返回結果集。

3.1.2 庫執行計劃存儲方式

數據庫生成執行計劃,是一個開銷很大的工作。因此,一般數據庫都會采取緩存策略。將生成好的執行計劃保存起來,下次可以重用,避免了再次生成產生開銷。在Oracle數據庫中有一塊內存區域稱為庫高速緩存(它是共享池的一部分)。用戶執行的SQL語句或者PL/SQL塊,其執行計劃會被緩存在這個區域中。當相同的SQL語句或者PL/SQL塊再次執行時,就可以直接利用緩存在該區域中的執行計劃,而不用再進行昂貴的解析操作。

在Oracle數據庫中,每條SQL語句都有一個稱為SQL_ID的唯一標識。在對一條SQL語句的解析中,Oracle會查詢在庫高速緩存中是否存在SQL_ID。如果不存在,則會申請一塊內存區域用來保存解析后的結果。在邏輯上,這塊內存區域保存的數據結構稱為游標。在內存區域中,一部分是與SQL語句相關的,被稱為父游標;另一部分是與語句的執行計劃相關的,被稱為子游標。從名字就可以看出,二者是有主從關系的。對于同一條SQL語句,可能會存在多個子游標,我們稱之為不同版本的子游標。不同的子游標的執行計劃可能相同,也可能不同,但它們都屬于同一個父游標。每個子游標都會被賦予一個序列號,即CHILD_NUMBER。一條語句生成的第一個游標的CHILD_NUMBER為0,相應的Oracle會為每個執行計劃生成一個哈希值以作區分。

下面通過一個實例,說明一下。


<user scott>
conn scott/tiger
select empno,ename from emp;
/*
當一條SQL第一次被執行的時候,Oracle會同時產生一個父游標(Parent Cursor)和一個子游標(Child Cursor)
*/

select sql_text,sql_id,version_count 
from v$sqlarea 
where sql_text like 'select empno,ename%';
SQL_TEXT                                           SQL_ID        VERSION_COUNT
-------------------------------------------------- ------------- -------------
select empno,ename from emp                        78bd3uh4a08av             1
/*
目標SQL在V$SQLAREA中只有一條匹配記錄,且這條記錄的VERSION_COUNT的值為1(VERSION_COUNT表示某個Parent Cursor擁有的所有Child Cursor的數量),這說明了Oracle在執行這條SQL時確實只產生了一個Parent Cursor和一個Child Cursor
*/

select plan_hash_value,child_number from v$sql where sql_id='78bd3uh4a08av';
PLAN_HASH_VALUE CHILD_NUMBER
--------------- ------------
     3956160932            0
/*
從V$SQL中查看所有Child Cursor的信息。根據SQL_ID查詢V$SQL,發現只有一條匹配記錄,而且這條記錄的CHILD_NUMBER的值為0(CHILD_NUMBER表示某個Child Cursor所對應的子游標號),說明Oracle在執行原目標SQL時確實只產生了一個編號為0的Child Cursor
*/

<user hf>
conn hf/hf
create table emp as select * from scott.emp;
select empno,ename from emp;
select sql_text,sql_id,version_count 
from v$sqlarea 
where sql_text like 'select empno,ename%';
SQL_TEXT                                           SQL_ID        VERSION_COUNT
-------------------------------------------------- ------------- -------------
select empno,ename from emp                        78bd3uh4a08av             2
/*
在V$SQLAREA中發現匹配記錄的VERSION_COUNTW為2,說明這個SQL語句有一個Parent Cursor和兩個Child Cursor
*/

select plan_hash_value,child_number from v$sql where sql_id='78bd3uh4a08av';
PLAN_HASH_VALUE CHILD_NUMBER
--------------- ------------
     3956160932            0
     3956160932            1
/*
查看V$SQL,可以看到CHILD_NUMBER的值分別為0和1,表示有兩個Child Cursor。這里產生兩個Child Cursor的原因是,雖然上面的SQL語句(“select empno,ename from emp”)看起來是一樣的,但是是由兩個不同用戶執行的,其實是兩個完全不同的SQL,所以要生成兩個游標,當然其對應的執行計劃也就不能共享了,對應著也就有了兩個不同的執行計劃
*/

主站蜘蛛池模板: 格尔木市| 尉犁县| 墨竹工卡县| 龙南县| 建始县| 无为县| 阜新| 墨竹工卡县| 武强县| 德江县| 桦南县| 祁连县| 那坡县| 威海市| 灌阳县| 建湖县| 甘肃省| 郎溪县| 贡山| 繁峙县| 贵定县| 五大连池市| 长沙市| 奇台县| 卢龙县| 嘉黎县| 焦作市| 涟水县| 九龙县| 溧阳市| 通海县| 民乐县| 清涧县| 汤阴县| 汶川县| 综艺| 台前县| 四子王旗| 尼木县| 会泽县| 壤塘县|