Oracle之SQL優化專題03-如何看懂SQL的執行計划


專題第一篇《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執行實際發生的遞歸調用、邏輯讀、物理讀、排序、處理行數等統計信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM