專題第一篇《Oracle之SQL優化專題01-查看SQL執行計划的方法》講到了查看SQL執行計划的方法,並介紹了各種方法的應用場景,那么這一篇就主要介紹下如何看懂SQL的執行計划。畢竟如果SQL的執行計划都看不懂,那優化就無從談起了。
關於如何看懂SQL的執行計划,我把它簡單分為3個部分:
1.判斷執行計划的執行順序
**口訣:**先子后父,先上后下。 一般簡單的執行計划可以直接根據這個口訣來判斷執行計划的執行順序。類似的口訣還有"最右最上"之類,其表達的意思都是一樣的,選擇一種自己易接受的口訣記憶即可。 舉一個簡單的例子:SQL> show user
USER is "SYS"
SQL> alter session set current_schema=scott;
SQL> alter session set statistics_level = ALL;
SQL> select a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b
2 where a.deptno = b.deptno and empno = 7788
3 ;
EMPNO ENAME DNAME JOB SAL
---------- ---------- -------------- --------- ----------
7788 SCOTT RESEARCH ANALYST 3000
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 19ktq5t44xsyp, child number 0
-------------------------------------
select a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where
a.deptno = b.deptno and empno = 7788
Plan hash value: 2385808155
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 3 | INDEX UNIQUE SCAN | PK_EMP | 1 | 1 | 1 |00:00:00.01 | 1 |
| 4 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 5 | INDEX UNIQUE SCAN | PK_DEPT | 1 | 1 | 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMPNO"=7788)
5 - access("A"."DEPTNO"="B"."DEPTNO")
24 rows selected.
執行計划的執行順序(按Id區分)就是 3 -> 2 -> 5 -> 4 -> 1 -> 0.
這里需要特別注意的是:
1)實際上這個所謂的口訣得到的執行順序只是為了方便我們理解操作數據的順序,而執行計划其實是按照Id從上往下遞歸調用的,簡單說其實優化器首先判斷是一條select語句,涉及多表關聯,關聯方式采用了NL Join,然后這個NL Join包含分別對EMP和DEPT索引回表的訪問。
2)理解了第一點,那對於SQL語句中select部分含有標量子查詢的部分不遵循這個口訣就更好理解了(因為實際這個標量子查詢是最后執行的)。
舉一個簡單例子說明這類標量子查詢:
SQL> select a.empno, a.ename, b.dname, a.job, a.sal, (select * from dual) x from emp a, dept b
2 where a.deptno = b.deptno and empno = 7788
3 ;
EMPNO ENAME DNAME JOB SAL X
---------- ---------- -------------- --------- ---------- -
7788 SCOTT RESEARCH ANALYST 3000 X
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID dpbagkc0c6ctv, child number 0
-------------------------------------
select a.empno, a.ename, b.dname, a.job, a.sal, (select * from dual) x
from emp a, dept b where a.deptno = b.deptno and empno = 7788
Plan hash value: 1121436124
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 4 |
| 1 | TABLE ACCESS FULL | DUAL | 1 | 1 | 1 |00:00:00.01 | 2 |
| 2 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 4 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 4 | INDEX UNIQUE SCAN | PK_EMP | 1 | 1 | 1 |00:00:00.01 | 1 |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | 1 | 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("EMPNO"=7788)
6 - access("A"."DEPTNO"="B"."DEPTNO")
25 rows selected.
執行計划的執行順序(按Id區分),如果按口訣來看就是 1 -> 4 -> 3 -> 6 -> 5 -> 2 -> 0.
而由於這其中1是標量子查詢,實際要最后執行,所以真實的執行順序為 4 -> 3 -> 6 -> 5 -> 2 -> 1 -> 0.
2.理解執行計划每步的含義
以上面的執行計划為例,要清楚知道每一列代表的含義: - Id 操作id號;-
Operation
這一列,可以看到SQL的每一步操作,深入理解就需要清楚表的主要訪問方式(TABLE ACCESS FULL、TABLE ACCESS BY INDEX ROWID、TABLE ACCESS BY USER ROWID),索引的主要訪問方式(INDEX UNIQUE SCAN、INDEX RANGE SCAN、INDEX FULL SCAN、INDEX FAST FULL SCAN、INDEX SKIP SCAN),多表查詢還需要清楚表連接的方法(NESTED LOOP JOIN、SORT MERGE JOIN、HASH JOIN、MERGE JOIN CARTESIAN)和順序(先訪問驅動表)等知識; -
Name
這一列,就是操作的具體對象名稱; -
Starts
這一列,代表訪問次數;比如在NESTED LOOP JOIN中,被驅動表的具體訪問次數就可以依據Starts的數值來判斷。 -
E-Rows
預估返回行數; -
A-Rows
實際返回行數; -
A-Time
實際執行時間; -
Buffers
邏輯讀塊/次;
如果是其他方式,看到的執行計划內容有所不同,比如可能就是下面這樣:
Execution Plan
----------------------------------------------------------
Plan hash value: 1121436124
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL | DUAL | 1 | 2 | 2 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 38 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 25 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
可以看到,前三列沒有區別,后面只有Rows、Bytes、Cost (%CPU)、Time等信息。
3.了解執行計划相關的信息
執行計划下面還有相關的信息,類似如下:Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("EMPNO"=7788)
6 - access("A"."DEPTNO"="B"."DEPTNO")
Note
-----
- SQL plan baseline SQL_PLAN_9sdj6nc4ybu5x2b78d17a used for this statement
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
879 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
-
謂詞信息(Predicate Information)
標識出哪一步有access、filter這樣的操作; -
Note部分(Note)
如果SQL執行中用到了動態采樣、基數反饋、自適應游標共享、SQL_Profile、SPM等,會在這里顯示出來; -
統計信息(Statistics)
這里指的是SQL執行實際發生的遞歸調用、邏輯讀、物理讀、排序、處理行數等統計信息。