生成、查看、理解Oracle的執行計劃

SQL是如何被執行的

SQL語句只是定義了要查詢什麼樣的數據,但是沒有定義數據是怎樣被查詢出來。

  1. 當DB接收到一條SQL查詢語句(INSERT, UPDATE, SELECT, DELETE)時,DB語法解析器(Parser)會先將其解析成一棵抽象語法樹(Abstract Syntax Tree, AST),解析器在不影響執行結果的前提下可能會重寫這個SQL來優化執行。
  2. 接下來這棵生成的語法樹會被送到DB的基於開銷的優化器(Cost-Based Optimizer, CBO),優化器會創建執行計劃(Execution Plan),並通過選擇join類型(Nested Loops, Merge Join, Hash Join等)、join順序、是否使用索引、使用什麼索引等方式找到使用開銷(CPU, IO, 內存)最小的計劃作爲最佳的執行計劃。
  3. 然後這個最佳的執行計劃被送到DB的執行器(Executor),執行器執行這個計劃,並將取到的數據返回給DB調用方。

SQL statement lifecycle

什麼是執行計劃

A statement's execution plan is the sequence of operations Oracle performs to run the statement. The EXPLAIN PLAN statement displays execution plans chosen by the Oracle optimizer for SELECTUPDATEINSERT, and DELETE statements.

執行計劃是DB優化器生成的,爲了完成一個SQL查詢的一系列必要且有序的數據庫操作(Operation)的集合。

生成執行計劃

預估執行計劃:每執行一次EXPLAIN PLAN,DB優化器(Optimizer)就會生成執行計劃而不執行SQL(因此叫“預估”),並寫入PLAN_TABLE表中(Oracle默認使用PLAN_TABLE臨時表來保存EXPLAIN PLAN的結果):

The PLAN_TABLE is automatically created as a global temporary table to hold the output of an EXPLAIN PLAN statement for all users. PLAN_TABLE is the default sample output table into which the EXPLAIN PLAN statement inserts rows describing execution plans

EXPLAIN PLAN
   SET STATEMENT_ID = 'statement1'  --指定該條語句的statement_id,後續可用於查詢PLAN_TABLE表中的執行計劃
   INTO my_plan_table               --可以不使用默認的PLAN_TABLE而使用自定義表
FOR
SELECT last_name FROM employees;    --要分析的SQL語句。INSERT, UPDATE, SELECT和DELETE語句。

實際執行計劃:在SQL執行的時候由DB優化器(Optimizer)創建的執行計劃。

EXPLAIN PLAN的執行結果代表在EXPLAIN這條SQL的時候,DB應該如何去執行這條SQL。而實際在執行的時候可能由於數據數量,結構(例如增加了索引等)等的變化,使得實際的執行計劃與預估執行計劃略有不同。

查詢執行計劃

預估執行計劃

DBMS_XPLAN.DISPLAY是一個Oracle自帶的函數(點擊鏈接查看詳情),它可以接受一些選項用於展示執行計劃:

--不帶選項
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

--這裏使用了自定義的表my_plan_table和自定義的SQL ID statement1
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY('my_plan_table','statement1','TYPICAL'));

執行結果大概長這樣:


Plan hash value: 1906736282

---------------------------------------------------------------------------------------------
| Id  | Operation             | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                     |     1 |    40 |     6   (0)| 00:00:01 |
|   1 |  NESTED LOOPS         |                     |     1 |    40 |     6   (0)| 00:00:01 |
|   2 |   MERGE JOIN CARTESIAN|                     |     4 |   128 |     6   (0)| 00:00:01 |
|*  3 |    TABLE ACCESS FULL  | PRODUCT_INFORMATION |     1 |    28 |     5   (0)| 00:00:01 |
|   4 |    BUFFER SORT        |                     |   105 |   420 |     1   (0)| 00:00:01 |
|   5 |     INDEX FULL SCAN   | ORDER_PK            |   105 |   420 |     1   (0)| 00:00:01 |
|*  6 |   INDEX UNIQUE SCAN   | ORDER_ITEMS_UK      |     1 |     8 |     0   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("MIN_PRICE"<40 AND "LIST_PRICE"<50)
   6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID")

19 rows selected.

實際執行計劃

由於實際執行計劃要在執行SQL時纔會生成,查看執行計劃前,要先執行SQL。DBMS_XPLAN.DISPLAY_CURSOR也是一個Oracle自帶的函數(點擊鏈接查看詳情),它可以展示已經加載到cache中的執行計劃:

--不帶選項,直接查詢最後一條SQL的執行計劃
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());

--指定了sql_id和child_number,查詢指定SQL的執行計劃。
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gwp663cqh5qbf',0));

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

--1.爲了區別其他的SQL,需要在執行SQL的時候加入特殊標記的註釋,比如:
SELECT /* TOTO */ ename, dname FROM dept d join emp e USING (deptno);

--2.通過SQL標記,查到sql_id和child_number。v$sql是一個系統視圖,保存執行過的sql語句
SELECT sql_id, child_number FROM v$sql WHERE sql_text LIKE '%TOTO%';

SQL_ID         CHILD_NUMBER
----------     -----------------------------
gwp663cqh5qbf   0

--3.再查詢該SQL的執行計劃
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gwp663cqh5qbf',0));

執行結果大概長這樣: 

Plan hash value: 3693697075, SQL ID: gwp663cqh5qbf, child number: 0
--------------------------------------------------------
SELECT /* TOTO */ ename, dname 
FROM dept d JOIN emp e USING (deptno);

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |       |       |     7 (100)|          |
|   1 |  SORT GROUP BY      |      |     4 |    64 |     7  (43)| 00:00:01 |
|*  2 |   HASH JOIN         |      |    14 |   224 |     6  (34)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPT |     4 |    44 |     3  (34)| 00:00:01 |
|   4 |    TABLE ACCESS FULL| EMP  |    14 |    70 |     3  (34)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("E"."DEPTNO"="D"."DEPTNO")

理解執行計劃

Oracle的執行計劃看起來是以tab符和行的方式展示的,實際上它是一棵執行樹,因此在閱讀執行計劃的時候,要從樹的葉子節點看起,即執行計劃表裏縮進最多的一行看起。

假如有以下SQL:

SELECT ename FROM emp e, dept d 
WHERE  e.deptno = d.deptno  
AND    e.empno=7369;

執行之後查詢實際執行計劃:

SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR());

結果:

Plan hash value: 3693697075, SQL hash value: 2096952573, child number: 0
------------------------------------------------------------------
SELECT ename FROM emp e, dept d WHERE e.deptno = d.deptno
AND e.empno=7369

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |            |          |
|*  1 |  HASH JOIN         |      |     1 |    16 |     6  (34)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| EMP  |     1 |    13 |     3  (34)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| DEPT |     4 |    12 |     3  (34)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("E"."DEPTNO"="D"."DEPTNO")
   2 - filter("E"."EMPNO"=7369)

21 rows selected.

可以看到,執行計劃包含三部分:執行計劃的hash值,以表格展示的執行計劃樹,以及Predicate信息。注意:表格的ID列中有*的行,代表該行在Predicate Information部分有更詳細信息。

ID=3,這是一個全表掃描DEPT的操作,有4行記錄共12字節。
ID=2,全表掃描EMP表,有1行記錄共13字節。在Predicate Information中看到,這個步驟還要過濾掉empno=7369的信息,而這正是sql中的where條件。
ID=1,兩個表進行hash join,得到1行記錄共16字節。在Predicate Information中看到,這個步驟join的方式是e.deptno=d.deptno,這也是sql種的where條件。
ID=0,執行SELECT操作,獲取結果。

 

參考地址:

https://docs.oracle.com/database/121/TGSQL/tgsql_genplan.htm#TGSQL271

https://docs.oracle.com/database/121/ARPLS/d_xplan.htm#ARPLS378

https://vladmihalcea.com/sql-execution-plan-oracle/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章