書名: 數據庫高效優化:架構、規范與SQL技巧作者名: 馬立和 高振嬌 韓鋒本章字數: 1868字更新時間: 2020-08-03 16:49:20
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,所以要生成兩個游標,當然其對應的執行計劃也就不能共享了,對應著也就有了兩個不同的執行計劃 */
- 劍破冰山:Oracle開發藝術
- 數據架構與商業智能
- WS-BPEL 2.0 Beginner's Guide
- 企業級數據與AI項目成功之道
- 高維數據分析預處理技術
- Hadoop大數據開發案例教程與項目實戰(在線實驗+在線自測)
- INSTANT Android Fragmentation Management How-to
- IPython Interactive Computing and Visualization Cookbook(Second Edition)
- Chef Essentials
- Spark分布式處理實戰
- Unreal Engine Virtual Reality Quick Start Guide
- 大數據數學基礎(R語言描述)
- Oracle 11g+ASP.NET數據庫系統開發案例教程
- Hands-On Deep Learning for Games
- AI Crash Course