ITPub博客

首页 > 数据库 > Oracle > 第86期 Oracle执行计划,彻底搞懂Oracle的执行计划

第86期 Oracle执行计划,彻底搞懂Oracle的执行计划

原创 Oracle 作者:pxbibm 时间:2026-10-27 16:12:09 0 删除 编辑

Oracle的执行计划,在哪里能找到执行?

对于经常和数据库打交道的朋友,优化sql时,最常见的就是执行计划,那么,什么是执行计划呢?市面上有非常多资料可以帮助理解执行计划,各类资料的实际参考效果参差不齐。

我个人认为讲解质量最高的资料来自《Troubleshooting Oracle Performance》(ISBN:1590599179)。

也可以参考 Oracle 11g 的官方文档也是最权威的,但是缺少一些实战的经验,也具备一定参考价值。

查看执行计划的手段同样多种多样,在较新版本的 Oracle中,甚至可以直接查询相关动态性能视图V$SQL_PLAN。

本文将演示几种生成执行计划的方式,同时介绍在获取 “真实准确的执行计划” 过程中可能会遇到的各类问题。

本篇博客会使用如下脚本创建测试表:

CREATE TABLE T1 (
  C1 NUMBER,
  C2 VARCHAR2(255),
  PRIMARY KEY (C1));
CREATE TABLE T2 (
  C1 NUMBER,
  C2 VARCHAR2(255),
  PRIMARY KEY (C1));
CREATE TABLE T3 (
  C1 NUMBER,
  C2 VARCHAR2(255));
CREATE TABLE T4 (
  C1 NUMBER,
  C2 VARCHAR2(255));
INSERT INTO
  T1
SELECT
  ROWNUM,
  RPAD(TO_CHAR(ROWNUM),255,'A')
FROM
  DUAL
CONNECT BY
  LEVEL<=1000000;
INSERT INTO
  T2
SELECT
  ROWNUM,
  RPAD(TO_CHAR(ROWNUM),255,'A')
FROM
  DUAL
CONNECT BY
  LEVEL<=1000000;
INSERT INTO
  T3
SELECT
  ROWNUM,
  RPAD(TO_CHAR(ROWNUM),255,'A')
FROM
  DUAL
CONNECT BY
  LEVEL<=1000000;
INSERT INTO
  T4
SELECT
  ROWNUM,
  RPAD(TO_CHAR(ROWNUM),255,'A')
FROM
  DUAL
CONNECT BY
  LEVEL<=1000000;
COMMIT;
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1',CASCADE=>TRUE)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T3',CASCADE=>TRUE)

面这段脚本会创建四张表,每张表包含 100 万行数据。前两张表由于定义了主键列,主键列上会自动生成索引。

除此之外,脚本会收集第一张和第三张表的统计信息,但不会收集第二张和第四张表的统计信息(用来配合后面几个演示场景)。

如果你使用的是 Oracle 10.1.0.1 或更高版本,可以通过 `DBMS_XPLAN.DISPLAY_CURSOR` 函数查看执行计划。

从 Oracle 10.1.0.1 版本开始,当在 SQL*Plus 中开启 AUTOTRACE 时,会自动调用这个函数。`DBMS_XPLAN.DISPLAY_CURSOR` 是查看执行计划的首选方式,

我们就从这个方法开始。

首先,我们在优化器模式(OPTIMIZER_MODE)设置为 ALL_ROWS 的情况下,生成一条 SQL 的执行计划:

SET LINESIZE 150
ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';
SELECT
  T3.C1,
  SUBSTR(T1.C2,1,10) C2
FROM
  T3,
  T1
WHERE
  T1.C1=T3.C1
  AND T1.C1 BETWEEN 1 AND 10;
 C1 C2
--- ----------
  1 1AAAAAAAAA
  2 2AAAAAAAAA
  3 3AAAAAAAAA
  4 4AAAAAAAAA
  5 5AAAAAAAAA
  6 6AAAAAAAAA
  7 7AAAAAAAAA
  8 8AAAAAAAAA
  9 9AAAAAAAAA
 10 10AAAAAAAA

查看刚执行完的 SQL 语句执行计划的常用方式如下

(注意:第三个参数,也就是 FORMAT 参数,也可以设为 NULL,效果完全一样):

在上面的输出中,可以留意到该执行计划的计算代价为 1893。

我们再做一组测试,将优化器模式设置为 FIRST_ROWS(在该模式下,Oracle 假定只会取回第一行数据,剩余行大概率不会被读取):

ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1';


SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'TYPICAL'));


SQL_ID  9dq71tc7vasgu, child number 1

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

SELECT   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,   T1 WHERE   T1.C1=T3.C1   AND

T1.C1 BETWEEN 1 AND 10


Plan hash value: 2674910673


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

| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT             |              |       |       |   575 (100)|          |

|   1 |  NESTED LOOPS                |              |     2 |   530 |   575   (4)| 00:00:03 |

|*  2 |   TABLE ACCESS FULL          | T3           |     7 |    35 |   568   (4)| 00:00:03 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |     1 |   260 |     2   (0)| 00:00:01 |

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0020554 |     1 |       |     1   (0)| 00:00:01 |

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


Predicate Information (identified by operation id):

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

   2 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))

   4 - access("T1"."C1"="T3"."C1")

       filter(("T1"."C1"<=10 AND "T1"."C1">=1))

   

从上面的结果可以看到,把优化器模式从 `ALL_ROWS` 改成 `FIRST_ROWS_1` 后,

生成了一份全新的执行计划(子游标编号加 1,计划哈希值也发生了变化):该计划改用嵌套循环连接,不再使用哈希连接,同时估算代价也变小了。


你可能会有疑问:既然嵌套循环这份计划的估算代价更低,为什么在 `OPTIMIZER_MODE=ALL_ROWS` 时,Oracle 没有选择它,

而是选了哈希连接?这个问题留到另一篇博客再讨论(当然,如果有人知道答案愿意分享也欢迎)。

比较奇怪的是,相比哈希连接的执行计划,这里对表 T3 返回行数的预估变少了,预估执行时间也更低。

但只看上面这些计划,我们其实无法判断真实性能好坏。

接下来我们重新做测试,修改 SQL 语句,在调用 `DBMS_XPLAN` 时传入格式参数 `'ALLSTATS LAST'`:    

ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  ddnbt67ftu9ds, child number 0

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN 1 AND 10


Plan hash value: 1519387310


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

| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |

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

|*  1 |  HASH JOIN                   |              |      1 |      9 |     10 |00:00:04.92 |   37144 |  37125 |  1452K|  1452K| 1328K (0)|

|*  2 |   TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:00.01 |   37138 |  37125 |       |       |          |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |      0 |       |       |          |

|*  4 |    INDEX RANGE SCAN          | SYS_C0020554 |      1 |     10 |     10 |00:00:00.01 |       4 |      0 |       |       |          |

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


Predicate Information (identified by operation id):

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

   1 - access("T1"."C1"="T3"."C1")

   2 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))

   4 - access("T1"."C1">=1 AND "T1"."C1"<=10)


这条查询耗时 4.92 秒执行完成,返回 10 行结果;在执行内存哈希连接的过程中,占用了 1328×1024 字节的内存。


有意思的是,对 T3 表的全表扫描只用了 0.01 秒,从磁盘读取了 37125 个数据块。

假设每次多块磁盘 IO 一次读取 128 个块,折算下来就是在 0.01 秒内发生 37125/128 =290.04 次多块读,平均每 1MB 的多块读只需要 0.000034 秒。

从这个执行计划看,明显是错误的,磁盘的读比固态硬盘还要快(继续往下看)


ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  ddnbt67ftu9ds, child number 1

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERE T1.C1=T3.C1   AND T1.C1 BETWEEN 1 AND 10


Plan hash value: 2674910673


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

| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |

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

|   1 |  NESTED LOOPS                |              |      1 |      2 |     10 |00:00:00.02 |   37171 |  37125 |

|*  2 |   TABLE ACCESS FULL          | T3           |      1 |      7 |     10 |00:00:00.02 |   37139 |  37125 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10 |00:00:00.01 |      32 |      0 |

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0020554 |     10 |      1 |     10 |00:00:00.01 |      22 |      0 |

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


Predicate Information (identified by operation id):

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

   2 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))

   4 - access("T1"."C1"="T3"."C1")

       filter(("T1"."C1"<=10 AND "T1"."C1">=1))


如上所示,在 `FIRST_ROWS_1` 优化器模式下,这条查询仅用 0.02 秒就执行完毕。但这里存在问题:执行计划显示从磁盘读取了 37125 个数据块,

和上一份执行计划一模一样。如果只看 ID 为 1 这一步的 A-Time 列来推算,相当于每次 1MB 的物理读只需要 0.000068 秒。

要是单纯错误解读执行计划、凭此做推断就能得到这种结果。

`GATHER_PLAN_STATISTICS` 提示非常有用,它能让 `DBMS_XPLAN` 输出上一条 SQL 的真实执行统计信息。如果你不想使用这个 Hint,

也可以在会话级别把 `STATISTICS_LEVEL` 参数设置为 ALL,示例如下:


ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';

ALTER SESSION SET STATISTICS_LEVEL='ALL';


SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  9dq71tc7vasgu, child number 2

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

SELECT   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN 1 AND 10


Plan hash value: 1519387310


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

| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |

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

|*  1 |  HASH JOIN                   |              |      1 |      9 |     10 |00:00:05.59 |   37144 |  37125 |  1452K|  1452K| 1324K (0)|

|*  2 |   TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:05.59 |   37138 |  37125 |       |       |          |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |      0 |       |       |          |

|*  4 |    INDEX RANGE SCAN          | SYS_C0020554 |      1 |     10 |     10 |00:00:00.01 |       4 |      0 |       |       |          |

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


Predicate Information (identified by operation id):

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

   1 - access("T1"."C1"="T3"."C1")

   2 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))

   4 - access("T1"."C1">=1 AND "T1"."C1"<=10)

   

注意,修改 `STATISTICS_LEVEL` 参数触发了这条 SQL 的又一次硬解析,子游标编号现在变成了 2。

和使用 `GATHER_PLAN_STATISTICS` Hint 的场景不同,这次我们看到的是全表扫描的真实耗时,而不再是 0.01 秒。

仔细观察还能发现,因为修改了 `STATISTICS_LEVEL` 参数,SQL 执行时间从 4.92 秒增加到了 5.59 秒。

接下来我们再次测试,将优化器模式指定为 `FIRST_ROWS_1`。    

ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1';

SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  9dq71tc7vasgu, child number 3

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

SELECT   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN 1 AND 10


Plan hash value: 2674910673


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

| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |

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

|   1 |  NESTED LOOPS                |              |      1 |      2 |     10 |00:00:04.79 |   37171 |  37125 |

|*  2 |   TABLE ACCESS FULL          | T3           |      1 |      7 |     10 |00:00:04.79 |   37139 |  37125 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10 |00:00:00.01 |      32 |      0 |

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0020554 |     10 |      1 |     10 |00:00:00.01 |      22 |      0 |

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


Predicate Information (identified by operation id):

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

   2 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))

   4 - access("T1"."C1"="T3"."C1")

       filter(("T1"."C1"<=10 AND "T1"."C1">=1))


再次留意到这里发生了硬解析,子游标编号加 1。这一次,查询不再是 0.02 秒完成,而是耗时 4.79 秒,绝大部分时间都消耗在全表扫描上。

还有一处比较奇怪:优化器预估只会返回 2 行,而不是 9 行或者 10 行。

我们继续测试,这一次改用绑定变量,不再使用常量字面值:


ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';

ALTER SESSION SET STATISTICS_LEVEL='TYPICAL';


VARIABLE N1 NUMBER

VARIABLE N2 NUMBER

EXEC :N1:=1

EXEC :N2:=10


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  cvq22z77c8fww, child number 0

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2


Plan hash value: 3807353021


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows|   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |

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

|*  1 |  FILTER                       |              |      1 |        |     10|00:00:05.60 |   37144 |  37125 |       |       |          |

|*  2 |   HASH JOIN                   |              |      1 |      9 |     10|00:00:05.60 |   37144 |  37125 |  1452K|  1452K| 1328K (0)|

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |     10|00:00:00.69 |   37138 |  37125 |       |       |          |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10|00:00:00.01 |       6 |      0 |       |       |          |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |      1 |     10 |     10|00:00:00.01 |       4 |      0 |       |       |          |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   2 - access("T1"."C1"="T3"."C1")

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))

   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)

和之前看到的执行计划完全一样;受绑定变量窥视的影响,基数预估结果也保持不变。

区别在于,现在 ID 为 1 的步骤多了一个 FILTER 过滤操作,对应的谓词条件要求绑定变量 N1 小于等于绑定变量 N2—— 这是 SQL 中 BETWEEN 语法自动生成的谓词。

接下来我们把第二个绑定变量的值改成一个大得多的值:


EXEC :N2:=500000


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  cvq22z77c8fww, child number 0

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2


Plan hash value: 3807353021


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |Used-Tmp|

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

|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:18.48 |     105K|  61283 |   4995 |       |       |          |        |

|*  2 |   HASH JOIN                   |              |      1 |      9 |    500K|00:00:17.98 |     105K|  61283 |   4995 |    15M|  3722K|   18M (1)|  43008 |

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |    500K|00:00:01.51 |   37138 |  37125 |      0 |       |       |          |        |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |    500K|00:00:11.50 |   68480 |  19163 |      0 |       |       |          |        |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |      1 |     10 |    500K|00:00:01.00 |   25893 |    648 |      0 |       |       |          |        |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   2 - access("T1"."C1"="T3"."C1")

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))

   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)

   

可以看到,这次没有发生硬解析(SQL_ID 和子游标编号和之前保持一致),两份 DBMS_XPLAN 输出里的 E-Rows 列的值也完全相同。

Used-Tmp 列说明,上一次执行时哈希连接发生了溢盘,在 TEMP 临时表空间中占用了 43008×1024 字节的空间。

我们修改优化器模式,再重新测试一次:  

EXEC :N2:=10

ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  cvq22z77c8fww, child number 1

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERE T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2


Plan hash value: 3268213130


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows|   A-Time   | Buffers | Reads  |

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

|*  1 |  FILTER                       |              |      1 |        |     10|00:00:00.01 |   37171 |  37125 |

|   2 |   NESTED LOOPS                |              |      1 |      2 |     10|00:00:00.01 |   37171 |  37125 |

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |      7 |     10|00:00:00.01 |   37139 |  37125 |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10|00:00:00.01 |      32 |      0 |

|*  5 |     INDEX UNIQUE SCAN         | SYS_C0020554 |     10 |      1 |     10|00:00:00.01 |      22 |      0 |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))

   5 - access("T1"."C1"="T3"."C1")

       filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))


EXEC :N2:=500000


SELECT /*+ GATHER_PLAN_STATISTICS */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  cvq22z77c8fww, child number 1

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,

   T1 WHERET1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2


Plan hash value: 3268213130


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |

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

|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:18.03 |    1603K|  56267 |

|   2 |   NESTED LOOPS                |              |      1 |      2 |    500K|00:00:17.53 |    1603K|  56267 |

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |      7 |    500K|00:00:04.51 |   70472 |  37125 |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |    500K|      1 |    500K|00:00:13.43 |    1533K|  19142 |

|*  5 |     INDEX UNIQUE SCAN         | SYS_C0020554 |    500K|      1 |    500K|00:00:04.21 |    1033K|    637 |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))

   5 - access("T1"."C1"="T3"."C1")

       filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))

当优化器模式设置为 `FIRST_ROWS_1` 时,这条 SQL 执行耗时 18.03 秒;设置为 `ALL_ROWS` 时耗时 18.48 秒(而之前只取回 10 行数据的场景,耗时分别是 0.01 秒和 5.60 秒)。

这么一看,是不是意味着想要优化性能,就应该把 `OPTIMIZER_MODE` 设置成 `FIRST_ROWS_1`?简短回答:**并不是**。这个问题我们会放在后续博客中深入探讨。


接下来我们来看**未收集统计信息的表**。我们修改这条使用绑定变量的原始 SQL,让它访问两张表 —— 这两张表以及它们的索引都没有最新的统计信息:

EXEC :N2:=10

ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T4.C1,

  SUBSTR(T2.C2,1,10) C2

FROM

  T4,

  T2

WHERE

  T2.C1=T4.C1

  AND T2.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  94bv1jwkzcc38, child number 0

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T4.C1,   SUBSTR(T2.C2,1,10) C2 FROM   T4,

   T2 WHERE   T2.C1=T4.C1   AND T2.C1 BETWEEN :N1 AND :N2


Plan hash value: 3374068464


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows|   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |

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

|*  1 |  FILTER                       |              |      1 |        |     10|00:00:00.01 |   74124 |  36361 |       |       |          |

|*  2 |   HASH JOIN                   |              |      1 |     10 |     10|00:00:00.01 |   74124 |  36361 |   711K|   711K| 1087K (0)|

|   3 |    TABLE ACCESS BY INDEX ROWID| T2           |      1 |     10 |     10|00:00:00.01 |       5 |      1 |       |       |          |

|*  4 |     INDEX RANGE SCAN          | SYS_C0020555 |      1 |     10 |     10|00:00:00.01 |       3 |      0 |       |       |          |

|*  5 |    TABLE ACCESS FULL          | T4           |      1 |    408 |     10|00:00:00.01 |   74119 |  36360 |       |       |          |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   2 - access("T2"."C1"="T4"."C1")

   4 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)

   5 - filter(("T4"."C1">=:N1 AND "T4"."C1"<=:N2))


Note

-----

   - dynamic sampling used for this statement

   

里唯一的变化是:Oracle 现在预估返回 10 行,而不是之前看到的 9 行;

另外在谓词信息下方有一行备注,标明本次使用了动态采样。还有一点,执行计划里 `HASH JOIN` 下方两行行源的顺序发生了变化。

ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T4.C1,

  SUBSTR(T2.C2,1,10) C2

FROM

  T4,

  T2

WHERE

  T2.C1=T4.C1

  AND T2.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  94bv1jwkzcc38, child number 1

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T4.C1,   SUBSTR(T2.C2,1,10) C2 FROM   T4,

   T2 WHERE   T2.C1=T4.C1   AND T2.C1 BETWEEN :N1 AND :N2


Plan hash value: 3374068464


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows|   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |

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

|*  1 |  FILTER                       |              |      1 |        |     10|00:00:00.01 |   37143 |  37120 |       |       |          |

|*  2 |   HASH JOIN                   |              |      1 |     10 |     10|00:00:00.01 |   37143 |  37120 |   711K|   711K| 1087K (0)|

|   3 |    TABLE ACCESS BY INDEX ROWID| T2           |      1 |     10 |     10|00:00:00.01 |       4 |      0 |       |       |          |

|*  4 |     INDEX RANGE SCAN          | SYS_C0020555 |      1 |     10 |     10|00:00:00.01 |       3 |      0 |       |       |          |

|*  5 |    TABLE ACCESS FULL          | T4           |      1 |   2307 |     10|00:00:00.01 |   37139 |  37120 |       |       |          |

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


Predicate Information (identified by operation id):

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

   1 - filter(:N1<=:N2)

   2 - access("T2"."C1"="T4"."C1")

   4 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)

   5 - filter(("T4"."C1">=:N1 AND "T4"."C1"<=:N2))


Note

-----

   - dynamic sampling used for this statement

好的,和 SQL 访问 T1、T3 表时相比,这份执行计划发生了一些变化。执行计划不再使用嵌套循环连接,实际上,它采用的和优化器模式设为 `ALL_ROWS` 时一样的计划。


Oracle 9i 以及更早版本的默认优化器模式是 `CHOOSE`,我们看看在这两张表上使用该优化器模式会是什么效果:

ALTER SESSION SET OPTIMIZER_MODE='CHOOSE';


SELECT /*+ GATHER_PLAN_STATISTICS */

  T4.C1,

  SUBSTR(T2.C2,1,10) C2

FROM

  T4,

  T2

WHERE

  T2.C1=T4.C1

  AND T2.C1 BETWEEN :N1 AND :N2;


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));


SQL_ID  94bv1jwkzcc38, child number 2

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T4.C1,   SUBSTR(T2.C2,1,10) C2 FROM   T4,

   T2 WHERE T2.C1=T4.C1   AND T2.C1 BETWEEN :N1 AND :N2


Plan hash value: 1544755769


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

| Id  | Operation                    | Name         | Starts | A-Rows |   A-Time   | Buffers | Reads  |

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

|   1 |  NESTED LOOPS                |              |      1 |     10 |00:00:00.02 |    2039K|  39001 |

|   2 |   TABLE ACCESS FULL          | T4           |      1 |   1000K|00:00:02.02 |   37139 |  37125 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T2           |   1000K|     10 |00:00:06.80 |    2001K|   1876 |

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0020555 |   1000K|     10 |00:00:04.78 |    2001K|   1876 |

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


Predicate Information (identified by operation id):

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

   4 - access("T2"."C1"="T4"."C1")

       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))


Note

-----

   - rule based optimizer used (consider using cbo)



注意执行计划的备注部分标明使用了**基于规则的优化器(RBO)**,然而 Oracle 官方文档(http://download.oracle.com/docs/cd/B19306_01/server.102/b14238/compat.htm)写明,

从 Oracle 10.1 版本开始,就已经不再支持 RULE 规则优化器。

另外还能看到,当前执行计划改用嵌套循环连接,FILTER 过滤算子已经消失;并且在`NESTED LOOPS`算子下方,全表扫描排在第一位,这和优化器模式设置为`FIRST_ROWS_1`、查询访问 T1 与 T3 表时的表现完全一致。


我们假设 C1 列存储的是信用卡号。

接下来做一项实验:获取`SQL_ID = cvq22z77c8fww`的全部子游标,同时拿到该 SQL 第一次硬解析时传入的绑定变量值。

**生产环境要谨慎管控该功能的访问权限,因为它可能泄露敏感数据。**

SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY_CURSOR('cvq22z77c8fww',NULL,'ALLSTATS LAST +PEEKED_BINDS'));


PLAN_TABLE_OUTPUT                                                                                                                                    

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

SQL_ID  cvq22z77c8fww, child number 0                                                                                                                

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,   T1 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2            


Plan hash value: 3807353021                                                                                                                          


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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem | Used-Tmp|

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

|*  1 |  FILTER                       |              |      1 |        |     10 |00:00:04.28 |   37144 |  37125 |       |       |          |         |

|*  2 |   HASH JOIN                   |              |      1 |      9 |     10 |00:00:04.28 |   37144 |  37125 |  1452K|  1452K| 2315K (0)|         |

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:00.01 |   37138 |  37125 |       |       |          |         |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |      0 |       |       |          |         |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |      1 |     10 |     10 |00:00:00.01 |       4 |      0 |       |       |          |         |

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


Peeked Binds (identified by position):                                                                                                               

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

   1 - (NUMBER): 1                                                                                                                                   

   2 - (NUMBER): 10                                                                                                                                  


Predicate Information (identified by operation id):                                                                                                  

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

   1 - filter(:N1<=:N2)                                                                                                                              

   2 - access("T1"."C1"="T3"."C1")                                                                                                                   

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))                                                                                                   

   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)                                                                                                     


-


SQL_ID  cvq22z77c8fww, child number 1                                                                                                                

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T3.C1,   SUBSTR(T1.C2,1,10) C2 FROM   T3,   T1 WHERE                                                          

T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2                                                                                                          


Plan hash value: 3268213130                                                                                                                          

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

| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |                                    

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

|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:18.03 |    1603K|  56267 |                                    

|   2 |   NESTED LOOPS                |              |      1 |      2 |    500K|00:00:17.53 |    1603K|  56267 |                                    

|*  3 |    TABLE ACCESS FULL          | T3           |      1 |      7 |    500K|00:00:04.51 |   70472 |  37125 |                                    

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |    500K|      1 |    500K|00:00:13.43 |    1533K|  19142 |                                    

|*  5 |     INDEX UNIQUE SCAN         | SYS_C0020554 |    500K|      1 |    500K|00:00:04.21 |    1033K|    637 |                                    

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


Peeked Binds (identified by position):                                                                                                               

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

   1 - (NUMBER): 1                                                                                                                                   

   2 - (NUMBER): 10                                                                                                                                  


Predicate Information (identified by operation id):                                                                                                  

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

   1 - filter(:N1<=:N2)                                                                                                                              

   3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))                                                                                                   

   5 - access("T1"."C1"="T3"."C1")                                                                                                                   

       filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1)) 


-


SQL_ID  94bv1jwkzcc38, child number 2

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

SELECT /*+ GATHER_PLAN_STATISTICS */   T4.C1,   SUBSTR(T2.C2,1,10) C2 FROM   T4,

   T2 WHERET2.C1=T4.C1   AND T2.C1 BETWEEN :N1 AND :N2


Plan hash value: 1544755769


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

| Id  | Operation                    | Name         | Starts | A-Rows |   A-Time   | Buffers | Reads  |

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

|   1 |  NESTED LOOPS                |              |      1 |     10 |00:00:00.02 |    2039K|  39001 |

|   2 |   TABLE ACCESS FULL          | T4           |      1 |   1000K|00:00:02.02 |   37139 |  37125 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T2           |   1000K|     10 |00:00:06.80 |    2001K|   1876 |

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0020555 |   1000K|     10 |00:00:04.78 |    2001K|   1876 |

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


Predicate Information (identified by operation id):

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

   4 - access("T2"."C1"="T4"."C1")

       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))


Note

-----

   - rule based optimizer used (consider using cbo)

想要了解更多关于 `DBMS_XPLAN.DISPLAY_CURSOR` 的内容,可以查阅官方文档:http://download.oracle.com/docs/cd/E11882_01/appdev.112/e10577/d_xplan.htm#sthref14168。


那么,如果我们想知道**这些子游标为什么会被创建**,该怎么做?我们可以执行类似下面这样的查询:


DESC V$SQL_SHARED_CURSOR


SET LINESIZE 200

SET HEADING ON

BREAK ON SQL_ID SKIP 1 


SELECT

  *

FROM

  V$SQL_SHARED_CURSOR

WHERE

  SQL_ID='94bv1jwkzcc38';


SQL_ID        ADDRESS          CHILD_ADDRESS    CHILD_NUMBER USOOSLSEBPISTABDLTRIIRLIOSMUTNFAITDLDBPCSRPTMBMROPMFL

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

94bv1jwkzcc38 000007FF9829D308 000007FFA68C1038            0 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN

              000007FF9829D308 000007FF94E0D950            1 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNN

              000007FF9829D308 000007FF98046318            2 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNN

  

上面的结果表明,子游标之所以被创建,原因是**优化器模式不匹配**(没错,我们修改过 `OPTIMIZER_MODE`)。

我们也可以检查绑定变量的定义(本例中其实不需要,因为我们已经在 `DBMS_XPLAN` 里使用了未公开的格式参数 `PEEKED_BINDS`,查看了大部分相关信息):

SELECT

  S.CHILD_NUMBER CN,

  SBM.*

FROM

  V$SQL_BIND_METADATA SBM,

  V$SQL S

WHERE

  S.SQL_ID='94bv1jwkzcc38'

  AND S.CHILD_ADDRESS=SBM.ADDRESS

ORDER BY

  S.CHILD_NUMBER,

  SBM.POSITION;


 CN ADDRESS            POSITION   DATATYPE MAX_LENGTH  ARRAY_LEN BIND_NAME

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

  0 000007FFA68C1038          1          2         22          0 N1

  0 000007FFA68C1038          2          2         22          0 N2

  1 000007FF94E0D950          1          2         22          0 N1

  1 000007FF94E0D950          2          2         22          0 N2

  2 000007FF98046318          1          2         22          0 N1

  2 000007FF98046318          2          2         22          0 N2


好了,刚才我们稍微跑题了,现在回到查看执行计划这个主题上来。上面这些示例展示的是 SQL**实际执行所采用的执行计划**,

它和 `explain plan` 生成的预估计划有可能不一样。我们来试一试 `EXPLAIN PLAN FOR` 语法(`DBMS_XPLAN.DISPLAY` 在 Oracle 9.2.0.1 及以上版本可用):  

ALTER SYSTEM FLUSH SHARED_POOL;


ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS';


EXPLAIN PLAN FOR

SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN 1 AND 10;


Explained.


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY());


Plan hash value: 1519387310


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

| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT             |              |     9 |  2385 |  1893   (4)| 00:00:09 |

|*  1 |  HASH JOIN                   |              |     9 |  2385 |  1893   (4)| 00:00:09 |

|*  2 |   TABLE ACCESS FULL          | T3           |    10 |    50 |  1888   (4)| 00:00:09 |

|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |    10 |  2600 |     4   (0)| 00:00:01 |

|*  4 |    INDEX RANGE SCAN          | SYS_C0020554 |    10 |       |     3   (0)| 00:00:01 |

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


Predicate Information (identified by operation id):

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

   1 - access("T1"."C1"="T3"."C1")

   2 - filter("T3"."C1"<=10 AND "T3"."C1">=1)

   4 - access("T1"."C1">=1 AND "T1"."C1"<=10)

   

上面这份计划看起来和第一条真实执行计划完全相同。接下来我们使用绑定变量再做一次测试:

VARIABLE N1 NUMBER

VARIABLE N2 NUMBER

EXEC :N1:=1

EXEC :N2:=10


EXPLAIN PLAN FOR

SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


Explained.


SELECT

  *

FROM

  TABLE(DBMS_XPLAN.DISPLAY());


Plan hash value: 3807353021


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

| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT              |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  1 |  FILTER                       |              |       |       |            |          |

|*  2 |   HASH JOIN                   |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 |  1905   (5)| 00:00:10 |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   634K|   178   (0)| 00:00:01 |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |  4500 |       |    11   (0)| 00:00:01 |

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


Predicate Information (identified by operation id):

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

   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))

   2 - access("T1"."C1"="T3"."C1")

   3 - filter("T3"."C1">=TO_NUMBER(:N1) AND "T3"."C1"<=TO_NUMBER(:N2))

   5 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))

可以留意到,在上面结果的谓词信息部分,每个绑定变量外面都包裹了 `TO_NUMBER` 转换,

尽管这些绑定变量本身定义的就是 NUMBER 数据类型。代价值也出现了小幅上升。


接下来我们使用 AUTOTRACE 查看执行计划(从 Oracle 10.1.0.1 版本开始,AUTOTRACE 底层会调用 DBMS_XPLAN 输出格式化后的执行计划)。   

SET AUTOTRACE TRACEONLY EXPLAIN


SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


Execution Plan

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

Plan hash value: 3807353021


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

| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT              |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  1 |  FILTER                       |              |       |       |            |          |

|*  2 |   HASH JOIN                   |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 |  1905   (5)| 00:00:10 |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   634K|   178   (0)| 00:00:01 |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |  4500 |       |    11   (0)| 00:00:01 |

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


Predicate Information (identified by operation id):

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

   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))

   2 - access("T1"."C1"="T3"."C1")

   3 - filter("T3"."C1">=TO_NUMBER(:N1) AND "T3"."C1"<=TO_NUMBER(:N2))

   5 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))

好的,在 Oracle 10.1.0.1 及以上版本中,这里的输出和前面示例是一样的,谓词信息部分依旧存在不正确的内容。

我们再同时查看运行时统计信息以及 explain plan:

SET AUTOTRACE TRACEONLY STATISTICS EXPLAIN


SELECT

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2;


10 rows selected.


Execution Plan

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

Plan hash value: 3807353021


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

| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT              |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  1 |  FILTER                       |              |       |       |            |          |

|*  2 |   HASH JOIN                   |              |  2499 |   646K|  2084   (5)| 00:00:10 |

|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 |  1905   (5)| 00:00:10 |

|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   634K|   178   (0)| 00:00:01 |

|*  5 |     INDEX RANGE SCAN          | SYS_C0020554 |  4500 |       |    11   (0)| 00:00:01 |

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


Predicate Information (identified by operation id):

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

   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))

   2 - access("T1"."C1"="T3"."C1")

   3 - filter("T3"."C1">=TO_NUMBER(:N1) AND "T3"."C1"<=TO_NUMBER(:N2))

   5 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))


Statistics

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

          0  recursive calls

          0  db block gets

      37144  consistent gets

      37125  physical reads

          0  redo size

        579  bytes sent via SQL*Net to client

        334  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

         10  rows processed


请注意:上面这份计划**未必是真实执行计划**,哪怕我们同时看到了真实的运行时统计。这一点很容易让人困惑:我们看到的是一份粗略估算出来的执行计划,搭配着真实的执行统计数据。

想要了解更多 `EXPLAIN PLAN` 的用法,可参考官方文档:http://download.oracle.com/docs/cd/E11882_01/server.112/e10821/ex_plan.htm。

再来做一组测试。这一次我们配置 Oracle,让它每次发生硬解析时,都把真实执行计划写入跟踪文件。我们通过在 SQL 语句中增加注释的方式,强制触发一次硬解析:

ALTER SESSION SET TRACEFILE_IDENTIFIER = '10132_HARD_PARSE';
ALTER SESSION SET EVENTS '10132 TRACE NAME CONTEXT FOREVER, LEVEL 1';
SELECT /* TEST */
  T3.C1,
  SUBSTR(T1.C2,1,10) C2
FROM
  T3,
  T1
WHERE
  T1.C1=T3.C1
  AND T1.C1 BETWEEN :N1 AND :N2;
ALTER SESSION SET EVENTS '10132 TRACE NAME CONTEXT OFF';

如果我们查看跟踪文件里的输出内容,可能会看到类似下面这样的信息:


sql_id=3g3fc5qyju0j3.

Current SQL statement for this session:

SELECT /* TEST */

  T3.C1,

  SUBSTR(T1.C2,1,10) C2

FROM

  T3,

  T1

WHERE

  T1.C1=T3.C1

  AND T1.C1 BETWEEN :N1 AND :N2


============

Plan Table

============

-----------------------------------------------------+-----------------------------------+

| Id  | Operation                      | Name        | Rows  | Bytes | Cost  | Time      |

-----------------------------------------------------+-----------------------------------+

| 0   | SELECT STATEMENT               |             |       |       |  1893 |           |

| 1   |  FILTER                        |             |       |       |       |           |

| 2   |   HASH JOIN                    |             |     9 |  2385 |  1893 |  00:00:09 |

| 3   |    TABLE ACCESS FULL           | T3          |    10 |    50 |  1888 |  00:00:09 |

| 4   |    TABLE ACCESS BY INDEX ROWID | T1          |    10 |  2600 |     4 |  00:00:01 |

| 5   |     INDEX RANGE SCAN           | SYS_C0020554|    10 |       |     3 |  00:00:01 |

-----------------------------------------------------+-----------------------------------+

Predicate Information:

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

1 - filter(:N1<=:N2)

2 - access("T1"."C1"="T3"."C1")

3 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))

5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)


Content of other_xml column

===========================

  db_version     : 10.2.0.2

  parse_schema   : TESTUSER

  plan_hash      : 3807353021

Peeked Binds

============

  Bind variable information

    position=1

    datatype(code)=2

    datatype(string)=NUMBER

    precision=0

    scale=0

    max length=22

    value=1

  Bind variable information

    position=2

    datatype(code)=2

    datatype(string)=NUMBER

    precision=0

    scale=0

    max length=22

    value=10

  Outline Data:

  /*+

    BEGIN_OUTLINE_DATA

      IGNORE_OPTIM_EMBEDDED_HINTS

      OPTIMIZER_FEATURES_ENABLE('10.2.0.1')

      ALL_ROWS

      OUTLINE_LEAF(@"SEL$1")

      FULL(@"SEL$1" "T3"@"SEL$1")

      INDEX(@"SEL$1" "T1"@"SEL$1" ("T1"."C1"))

      LEADING(@"SEL$1" "T3"@"SEL$1" "T1"@"SEL$1")

      USE_HASH(@"SEL$1" "T1"@"SEL$1")

    END_OUTLINE_DATA

  */


Optimizer environment:

  optimizer_mode_hinted               = false

  optimizer_features_hinted           = 0.0.0

  parallel_execution_enabled          = false

  parallel_query_forced_dop           = 0

  parallel_dml_forced_dop             = 0

  parallel_ddl_forced_degree          = 0

  parallel_ddl_forced_instances       = 0

  _query_rewrite_fudge                = 90

  optimizer_features_enable           = 10.2.0.1

  _optimizer_search_limit             = 5

  cpu_count                           = 4

  active_instance_count               = 1

  parallel_threads_per_cpu            = 2

  hash_area_size                      = 131072

  bitmap_merge_area_size              = 1048576

  sort_area_size                      = 65536

  sort_area_retained_size             = 0

  _sort_elimination_cost_ratio        = 0

  _optimizer_block_size               = 8192

  _sort_multiblock_read_count         = 2

  _hash_multiblock_io_count           = 0

  _db_file_optimizer_read_count       = 128

  _optimizer_max_permutations         = 2000

  pga_aggregate_target                = 204800 KB

  _pga_max_size                       = 204800 KB

  _query_rewrite_maxdisjunct          = 257

  _smm_auto_min_io_size               = 56 KB

  _smm_auto_max_io_size               = 248 KB

  _smm_min_size                       = 204 KB

  _smm_max_size                       = 40960 KB

  _smm_px_max_size                    = 102400 KB

  _cpu_to_io                          = 0

  _optimizer_undo_cost_change         = 10.2.0.1

  parallel_query_mode                 = enabled

  parallel_dml_mode                   = disabled

  parallel_ddl_mode                   = enabled

  optimizer_mode                      = all_rows

  sqlstat_enabled                     = false

  _optimizer_percent_parallel         = 101

  _always_anti_join                   = choose

  _always_semi_join                   = choose

  _optimizer_mode_force               = true

  _partition_view_enabled             = true

  _always_star_transformation         = false

  _query_rewrite_or_error             = false

  _hash_join_enabled                  = true

  cursor_sharing                      = exact

  _b_tree_bitmap_plans                = true

  star_transformation_enabled         = false

  _optimizer_cost_model               = choose

  _new_sort_cost_estimate             = true

  _complex_view_merging               = true

  _unnest_subquery                    = true

  _eliminate_common_subexpr           = true

  _pred_move_around                   = true

  _convert_set_to_join                = false

  _push_join_predicate                = true

  _push_join_union_view               = true

  _fast_full_scan_enabled             = true

  _optim_enhance_nnull_detection      = true

  _parallel_broadcast_enabled         = true

  _px_broadcast_fudge_factor          = 100

  _ordered_nested_loop                = true

  _no_or_expansion                    = false

  optimizer_index_cost_adj            = 100

  optimizer_index_caching             = 0

  _system_index_caching               = 0

  _disable_datalayer_sampling         = false

  query_rewrite_enabled               = true

  query_rewrite_integrity             = enforced

  _query_cost_rewrite                 = true

  _query_rewrite_2                    = true

  _query_rewrite_1                    = true

  _query_rewrite_expression           = true

  _query_rewrite_jgmigrate            = true

  _query_rewrite_fpc                  = true

  _query_rewrite_drj                  = true

  _full_pwise_join_enabled            = true

  _partial_pwise_join_enabled         = true

  _left_nested_loops_random           = true

  _improved_row_length_enabled        = true

  _index_join_enabled                 = true

  _enable_type_dep_selectivity        = true

  _improved_outerjoin_card            = true

  _optimizer_adjust_for_nulls         = true

  _optimizer_degree                   = 0

  _use_column_stats_for_function      = true

  _subquery_pruning_enabled           = true

  _subquery_pruning_mv_enabled        = false

  _or_expand_nvl_predicate            = true

  _like_with_bind_as_equality         = false

  _table_scan_cost_plus_one           = true

  _cost_equality_semi_join            = true

  _default_non_equality_sel_check     = true

  _new_initial_join_orders            = true

  _oneside_colstat_for_equijoins      = true

  _optim_peek_user_binds              = true

  _minimal_stats_aggregation          = true

  _force_temptables_for_gsets         = false

  workarea_size_policy                = auto

  _smm_auto_cost_enabled              = true

  _gs_anti_semi_join_allowed          = true

  _optim_new_default_join_sel         = true

  optimizer_dynamic_sampling          = 2

  _pre_rewrite_push_pred              = true

  _optimizer_new_join_card_computation = true

  _union_rewrite_for_gs               = yes_gset_mvs

  _generalized_pruning_enabled        = true

  _optim_adjust_for_part_skews        = true

  _force_datefold_trunc               = false

  statistics_level                    = typical

  _optimizer_system_stats_usage       = true

  skip_unusable_indexes               = true

  _remove_aggr_subquery               = true

  _optimizer_push_down_distinct       = 0

  _dml_monitoring_enabled             = true

  _optimizer_undo_changes             = false

  _predicate_elimination_enabled      = true

  _nested_loop_fudge                  = 100

  _project_view_columns               = true

  _local_communication_costing_enabled = true

  _local_communication_ratio          = 50

  _query_rewrite_vop_cleanup          = true

  _slave_mapping_enabled              = true

  _optimizer_cost_based_transformation = linear

  _optimizer_mjc_enabled              = true

  _right_outer_hash_enable            = true

  _spr_push_pred_refspr               = true

  _optimizer_cache_stats              = false

  _optimizer_cbqt_factor              = 50

  _optimizer_squ_bottomup             = true

  _fic_area_size                      = 131072

  _optimizer_skip_scan_enabled        = true

  _optimizer_cost_filter_pred         = false

  _optimizer_sortmerge_join_enabled   = true

  _optimizer_join_sel_sanity_check    = true

  _mmv_query_rewrite_enabled          = true

  _bt_mmv_query_rewrite_enabled       = true

  _add_stale_mv_to_dependency_list    = true

  _distinct_view_unnesting            = false

  _optimizer_dim_subq_join_sel        = true

  _optimizer_disable_strans_sanity_checks = 0

  _optimizer_compute_index_stats      = true

  _push_join_union_view2              = true

  _optimizer_ignore_hints             = false

  _optimizer_random_plan              = 0

  _query_rewrite_setopgrw_enable      = true

  _optimizer_correct_sq_selectivity   = true

  _disable_function_based_index       = false

  _optimizer_join_order_control       = 3

  _optimizer_cartesian_enabled        = true

  _optimizer_starplan_enabled         = true

  _extended_pruning_enabled           = true

  _optimizer_push_pred_cost_based     = true

  _sql_model_unfold_forloops          = run_time

  _enable_dml_lock_escalation         = false

  _bloom_filter_enabled               = true

  _update_bji_ipdml_enabled           = 0

  _optimizer_extended_cursor_sharing  = udo

  _dm_max_shared_pool_pct             = 1

  _optimizer_cost_hjsmj_multimatch    = true

  _optimizer_transitivity_retain      = true

  _px_pwg_enabled                     = true

  optimizer_secure_view_merging       = true

  _optimizer_join_elimination_enabled = true

  flashback_table_rpi                 = non_fbt

  _optimizer_cbqt_no_size_restriction = true

  _optimizer_enhanced_filter_push     = true

  _optimizer_filter_pred_pullup       = true

  _rowsrc_trace_level                 = 0

  _simple_view_merging                = true

  _optimizer_rownum_pred_based_fkr    = true

  _optimizer_better_inlist_costing    = all

  _optimizer_self_induced_cache_cost  = false

  _optimizer_min_cache_blocks         = 10

  _optimizer_or_expansion             = depth

  _optimizer_order_by_elimination_enabled = true

  _optimizer_outer_to_anti_enabled    = true

  _selfjoin_mv_duplicates             = true

  _dimension_skip_null                = true

  _force_rewrite_enable               = false

  _optimizer_star_tran_in_with_clause = true

  _optimizer_complex_pred_selectivity = true

  _optimizer_connect_by_cost_based    = false

  _gby_hash_aggregation_enabled       = true

  _globalindex_pnum_filter_enabled    = false

  _fix_control_key                    = 0

  _optimizer_skip_scan_guess          = false

  _enable_row_shipping                = false

  *********************************

  Bug Fix Control Environment

  ***************************

  fix  4611850 = disabled

  fix  4663804 = disabled

  fix  4663698 = disabled

  fix  4545833 = disabled

  fix  3499674 = disabled

  fix  4584065 = disabled

  fix  4602374 = disabled

  fix  4569940 = enabled

  fix  4631959 = disabled

  fix  4519340 = disabled

  fix  4550003 = enabled

  fix  4488689 = disabled

  fix  3118776 = enabled

  fix  4519016 = enabled

  fix  4487253 = enabled

  fix  4556762 = 0      

  fix  4728348 = disabled

  fix  4723244 = disabled

  fix  4554846 = disabled

  fix  4175830 = enabled

  fix  5240607 = disabled

  fix  4722900 = enabled

Query Block Registry:

*********************

SEL$1 0x122d9358 (PARSER) [FINAL]

Optimizer State Dump: call(in-use=53568, alloc=81816), compile(in-use=76816, alloc=126256)


注意,从上面的内容中,我们可以看到真实执行计划、被窥视的绑定变量、能够复现该执行计划的 Hint 集合,以及大量常规和隐藏的优化器参数。

我们可以生成级别为 1 的 10053 跟踪文件,用来查明优化器为何选择这份计划,但这里暂时先不演示。

下面是级别 4 的 10046 跟踪示例(我们必须在待分析 SQL 之后再执行一条其他 SQL,

这样目标 SQL 的 STAT 统计行才会写入跟踪文件):

ALTER SESSION SET TRACEFILE_IDENTIFIER = '10046_EXECUTION_PLAN';
ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 4';
SELECT /* TEST */
  T3.C1,
  SUBSTR(T1.C2,1,10) C2
FROM
  T3,
  T1
WHERE
  T1.C1=T3.C1
  AND T1.C1 BETWEEN :N1 AND :N2;
SELECT SYSDATE FROM DUAL;
生成的跟踪文件大致如下:
=====================
PARSING IN CURSOR #34 len=118 dep=0 uid=31 oct=3 lid=31 tim=2087403486 hv=3172794915 ad='9811b110'
SELECT /* TEST */
  T3.C1,
  SUBSTR(T1.C2,1,10) C2
FROM
  T3,
  T1
WHERE
  T1.C1=T3.C1
  AND T1.C1 BETWEEN :N1 AND :N2
END OF STMT
PARSE #34:c=0,e=162,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=2087403479
BINDS #34:
kkscoacd
 Bind#0
  oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
  oacflg=03 fl2=1000000 frm=00 csi=00 siz=48 off=0
  kxsbbbfp=13beb440  bln=22  avl=02  flg=05
  value=1
 Bind#1
  oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
  oacflg=03 fl2=1000000 frm=00 csi=00 siz=0 off=24
  kxsbbbfp=13beb458  bln=22  avl=02  flg=01
  value=10
EXEC #34:c=0,e=170,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=2087403808
FETCH #34:c=1078125,e=3875649,p=37124,cr=37142,cu=0,mis=0,r=1,dep=0,og=1,tim=2091279513
FETCH #34:c=0,e=180,p=0,cr=2,cu=0,mis=0,r=9,dep=0,og=1,tim=2091280273
STAT #34 id=1 cnt=10 pid=0 pos=1 obj=0 op='FILTER  (cr=37144 pr=37124 pw=0 time=3875627 us)'
STAT #34 id=2 cnt=10 pid=1 pos=1 obj=0 op='HASH JOIN  (cr=37144 pr=37124 pw=0 time=3875620 us)'
STAT #34 id=3 cnt=10 pid=2 pos=1 obj=114232 op='TABLE ACCESS FULL T3 (cr=37138 pr=37124 pw=0 time=19345 us)'
STAT #34 id=4 cnt=10 pid=2 pos=2 obj=114228 op='TABLE ACCESS BY INDEX ROWID T1 (cr=6 pr=0 pw=0 time=66 us)'
STAT #34 id=5 cnt=10 pid=4 pos=1 obj=114229 op='INDEX RANGE SCAN SYS_C0020554 (cr=4 pr=0 pw=0 time=37 us)'
=====================

当然,我们也可以直接从 10046 跟踪文件里读取真实执行计划、绑定变量的值以及数据类型。

不过这里我们改用 TKPROF 工具处理(使用 TKPROF 时,**无法看到绑定变量的值**)。

C:\> tkprof test_ora_4436_10046_execution_plan.trc test_ora_4436_10046_execution_plan.txt

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.07       3.87      37124      37144          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.07       3.87      37124      37144          0          10
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 31 
Rows     Row Source Operation
-------  ---------------------------------------------------
     10  FILTER  (cr=37144 pr=37124 pw=0 time=3875627 us)
     10   HASH JOIN  (cr=37144 pr=37124 pw=0 time=3875620 us)
     10    TABLE ACCESS FULL T3 (cr=37138 pr=37124 pw=0 time=19345 us)
     10    TABLE ACCESS BY INDEX ROWID T1 (cr=6 pr=0 pw=0 time=66 us)
     10     INDEX RANGE SCAN SYS_C0020554 (cr=4 pr=0 pw=0 time=37 us)(object id 114229)

当然,有一个相当普遍的误区:有些人会在 TKPROF 里使用 EXPLAIN,而不是直接解读**行源操作(Row Source Operation)**这部分内容(行源操作记录的才是真实发生的执行过程 —— 当然也有例外,详情可以参考这篇文章:   C:\>tkprof test_ora_4436_10046_execution_plan.trc test_ora_4436_10046_execution_plan.txt EXPLAIN=TESTUSER/TESTPASS

请注意,如果「行源操作(Row Source Operation)」里的计划和下方「执行计划(Execution Plan)」截然不同,

输出结果就非常容易让人混淆:前者是**真实执行计划**,而后者本质上只是 `EXPLAIN PLAN FOR` 这类预估计划:

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  FILTER  (cr=37144 pr=37124 pw=0 time=3875627 us)
     10   HASH JOIN  (cr=37144 pr=37124 pw=0 time=3875620 us)
     10    TABLE ACCESS FULL T3 (cr=37138 pr=37124 pw=0 time=19345 us)
     10    TABLE ACCESS BY INDEX ROWID T1 (cr=6 pr=0 pw=0 time=66 us)
     10     INDEX RANGE SCAN SYS_C0020554 (cr=4 pr=0 pw=0 time=37 us)(object id 114229)
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   MODE: ALL_ROWS
     10   FILTER
     10    HASH JOIN
     10     TABLE ACCESS   MODE: ANALYZED (FULL) OF 'T3' (TABLE)
     10     TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 'T1'
                (TABLE)
     10      INDEX   MODE: ANALYZED (RANGE SCAN) OF 'SYS_C0020554'
                 (INDEX (UNIQUE))

希望到这里,你已经明白什么是执行计划、去哪里查看它,以及需要避开哪些容易踩坑的陷阱。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12798004/viewspace-3186817/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论
【DBAplus社群】联合发起人。ACOUG核心成员,Oracle用户组年轻专家。熟悉Oracle的体系结构,擅长Oracle数据库架构规划、SQL优化、OWI方面性能的优化。曾服务于航天、航空、机车、大型制造行业数据库的维护以及系统开发工作,开发过基于Oracle数据库的大型ERP系统。
  • 博文量
    343
  • 访问量
    2472876