SQL是如何被執行的
SQL語句只是定義了要查詢什麼樣的數據,但是沒有定義數據是怎樣被查詢出來。
- 當DB接收到一條SQL查詢語句(INSERT, UPDATE, SELECT, DELETE)時,DB語法解析器(Parser)會先將其解析成一棵抽象語法樹(Abstract Syntax Tree, AST),解析器在不影響執行結果的前提下可能會重寫這個SQL來優化執行。
- 接下來這棵生成的語法樹會被送到DB的基於開銷的優化器(Cost-Based Optimizer, CBO),優化器會創建執行計劃(Execution Plan),並通過選擇join類型(Nested Loops, Merge Join, Hash Join等)、join順序、是否使用索引、使用什麼索引等方式找到使用開銷(CPU, IO, 內存)最小的計劃作爲最佳的執行計劃。
- 然後這個最佳的執行計劃被送到DB的執行器(Executor),執行器執行這個計劃,並將取到的數據返回給DB調用方。
什麼是執行計劃
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 forSELECT
,UPDATE
,INSERT
, andDELETE
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 anEXPLAIN
PLAN
statement for all users.PLAN_TABLE
is the default sample output table into which theEXPLAIN
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