ITPub博客

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

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

原创 Oracle 作者:pxbibm 时间:2026-11-04 12:33:11 0 删除 编辑

在上一篇《第86期 Oracle执行计划,彻底搞懂Oracle的执行计划》中,我们探究了多种生成执行计划的方法,具体来说,我们学习了如何完成以下操作:

1. 展示 SQL 语句**实际(TYPICAL 模式)**执行计划,包含基数预估与成本值。

2. 通过 GATHER_PLAN_STATISTICS Hint,展示 SQL 语句**实际(ALLSTATS LAST 模式)**执行计划,用来对比预估基数和计划里每一步操作返回的真实行数,同时获取语句最终实际执行时间(中间步骤的耗时不一定贴近真实耗时)。

3. 在会话级别将 STATISTICS_LEVEL 参数设置为 ALL,展示 SQL 语句**实际(ALLSTATS LAST 模式)**执行计划,可以拿到最终执行时间,并且中间步骤的耗时估算也更加贴近真实值(部分场景下,SQL 整体执行耗时会明显上升)。

4. 在 ALL_ROWS、FIRST_ROWS_1、CHOOSE 之间切换 OPTIMIZER_MODE 参数,对比实际执行计划的变化。注意:将优化器模式设置为 FIRST_ROWS_1 时,优化器可能低估返回行数,执行计划中就能观察到这个现象。

5. 使用 SQL*Plus 的 AUTOTRACE 和 EXPLAIN PLAN 展示一份看似正确的执行计划,并探究依赖这类计划时可能遇到的各类问题。

6. 观察绑定变量窥视(bind peeking)对执行计划带来的影响。

7. 通过设置 10132 事件来获取执行计划。

8. 直接从 10046 跟踪文件中读取执行计划。

9. 使用 TKPROF 工具处理跟踪文件,同时了解让 TKPROF 生成 Explain 计划时会引发的一个典型隐患。

本系列第一篇博客使用的是OPTIMIZER_FEATURES_ENABLE设置为 10.2.0.1。将 64 位 Oracle 11.2.0.1 的输出结果与 Oracle 10.2.0.2 做对比会很有参考价值。通过对比输出结果,或许可以解答一个疑问:Oracle 10.2.0.2 是否存在某个 bug?比如当OPTIMIZER_MODE设置为FIRST_ROWS_1时,执行计划中展示的基数估算是否异常。如果 11.2.0.1 中也出现同样输出,那大概率说明优化器就是按照该设计逻辑运行的。

当然,本文会沿用第一篇博客中定义的 T1、T2、T3、T4 四张表。

首先,我们将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,效果相同)。

#SELECT
#  *
#FROM
#  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'TYPICAL'));
#
#SQL_ID  9dq71tc7vasgu, child number 0
#-------------------------------------
#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: 985299768          
#
#---------------------------------------------------------------------------------------------
#| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#---------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT             |              |       |       | 10219 (100)|          |
#|*  1 |  HASH JOIN                   |              |     9 |  2394 | 10219   (1)| 00:02:03 |
#|*  2 |   TABLE ACCESS FULL          | T3           |    10 |    50 | 10214   (1)| 00:02:03 |
#|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |    10 |  2610 |     4   (0)| 00:00:01 |
#|*  4 |    INDEX RANGE SCAN          | SYS_C0017307 |    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)

请注意上面输出中,该执行计划的计算成本为 10219(无负载 CPU 统计模式)。

接下来我们再做一组测试,将 OPTIMIZER_MODE 设置为 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: 2322422896         
#
#---------------------------------------------------------------------------------------------
#| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#---------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT             |              |       |       |  3073 (100)|          |
#|   1 |  NESTED LOOPS                |              |       |       |            |          |
#|   2 |   NESTED LOOPS               |              |     2 |   532 |  3073   (1)| 00:00:37 |
#|*  3 |    TABLE ACCESS FULL         | T3           |     7 |    35 |  3066   (1)| 00:00:37 |
#|*  4 |    INDEX UNIQUE SCAN         | SYS_C0017307 |     1 |       |     1   (0)| 00:00:01 |
#|   5 |   TABLE ACCESS BY INDEX ROWID| T1           |     1 |   261 |     2   (0)| 00:00:01 |
#---------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                     
#   3 - 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,计划哈希值也发生了变化)。该新计划采用两次嵌套循环连接(先将表 T1 的索引与表 T3 做连接),而不再使用哈希连接,计算出的成本下降到了 3073。你可能会有疑问:既然这个计划的估算成本更低,为什么当OPTIMIZER_MODE设置为ALL_ROWS时,Oracle 没有选择这个嵌套循环连接的计划,而是选择哈希连接?

这里注意,Oracle 11.2.0.1 估算的返回行数和 Oracle 10.2.0.2 是一样的。但仅仅通过上面这些计划,我们其实无法判断真实性能好坏。因此我们重新做测试,修改 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: 985299768          
#
#----------------------------------------------------------------------------------------------------------------------------------
#| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
#----------------------------------------------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT             |              |      1 |        |     10 |00:00:00.07 |   37170 |       |       |          |
#|*  1 |  HASH JOIN                   |              |      1 |      9 |     10 |00:00:00.07 |   37170 |  1452K|  1452K| 1365K (0)|
#|*  2 |   TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:00.01 |   37164 |       |       |          |
#|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |       |       |          |
#|*  4 |    INDEX RANGE SCAN          | SYS_C0017307 |      1 |     10 |     10 |00:00:00.01 |       4 |       |       |          |
#----------------------------------------------------------------------------------------------------------------------------------
#
#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)

这条查询耗时 0.07 秒执行完毕,返回 10 行数据;在内存哈希连接阶段,占用了 1365×1024 字节的内存。

没有从磁盘读取任何数据块(该查询之前已经执行过,表和索引的数据块保留在缓冲区缓存中)。

#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: 2322422896         
#
#-------------------------------------------------------------------------------------------------------                        
#| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                        
#-------------------------------------------------------------------------------------------------------                        
#|   0 | SELECT STATEMENT             |              |      1 |        |     10 |00:00:00.01 |   37189 |                        
#|   1 |  NESTED LOOPS                |              |      1 |        |     10 |00:00:00.01 |   37189 |                        
#|   2 |   NESTED LOOPS               |              |      1 |      2 |     10 |00:00:00.01 |   37179 |                        
#|*  3 |    TABLE ACCESS FULL         | T3           |      1 |      7 |     10 |00:00:00.01 |   37165 |                        
#|*  4 |    INDEX UNIQUE SCAN         | SYS_C0017307 |     10 |      1 |     10 |00:00:00.01 |      14 |                        
#|   5 |   TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10 |00:00:00.01 |      10 |                        
#-------------------------------------------------------------------------------------------------------                        
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                      
#
#   3 - 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.01 秒就执行完成。

GATHER_PLAN_STATISTICS提示非常有用,它可以让DBMS_XPLAN输出上一条 SQL 语句的真实执行统计信息。

如果不想使用该提示,也可以在会话级别将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: 985299768          
#
#----------------------------------------------------------------------------------------------------------------------------------
#| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
#----------------------------------------------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT             |              |      1 |        |     10 |00:00:00.07 |   37170 |       |       |          |
#|*  1 |  HASH JOIN                   |              |      1 |      9 |     10 |00:00:00.07 |   37170 |  1452K|  1452K| 1424K (0)|
#|*  2 |   TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:00.07 |   37164 |       |       |          |
#|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |       |       |          |
#|*  4 |    INDEX RANGE SCAN          | SYS_C0017307 |      1 |     10 |     10 |00:00:00.01 |       4 |       |       |          |
#----------------------------------------------------------------------------------------------------------------------------------
#
#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。

不知何种原因,哈希连接所需的内存量出现了小幅上升。仔细观察还能发现,修改STATISTICS_LEVEL参数前后,执行耗时保持不变。

我们再次测试,将 OPTIMIZER_MODE设置为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: 2322422896         
#
#-------------------------------------------------------------------------------------------------------                        
#| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                        
#-------------------------------------------------------------------------------------------------------                        
#|   0 | SELECT STATEMENT             |              |      1 |        |     10 |00:00:00.07 |   37189 |                        
#|   1 |  NESTED LOOPS                |              |      1 |        |     10 |00:00:00.07 |   37189 |                        
#|   2 |   NESTED LOOPS               |              |      1 |      2 |     10 |00:00:00.07 |   37179 |                        
#|*  3 |    TABLE ACCESS FULL         | T3           |      1 |      7 |     10 |00:00:00.07 |   37165 |                        
#|*  4 |    INDEX UNIQUE SCAN         | SYS_C0017307 |     10 |      1 |     10 |00:00:00.01 |      14 |                        
#|   5 |   TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10 |00:00:00.01 |      10 |                        
#-------------------------------------------------------------------------------------------------------                        
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                     
#   3 - filter(("T3"."C1"<=10 AND "T3"."C1">=1))                         
#   4 - access("T1"."C1"="T3"."C1")  
#       filter(("T1"."C1"<=10 AND "T1"."C1">=1))

再次留意,这里发生了一次硬解析,子游标号加 1。这一次,查询不再是 0.01 秒完成,而是耗时 0.07 秒。

我们再试一次,这次改用绑定变量,而不是常量(字面值):

#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: 744086632          
#
#-----------------------------------------------------------------------------------------------------------------------------------                                
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |                                
#-----------------------------------------------------------------------------------------------------------------------------------                                
#|   0 | SELECT STATEMENT              |              |      1 |        |     10 |00:00:00.07 |   37170 |       |       |          |                                
#|*  1 |  FILTER                       |              |      1 |        |     10 |00:00:00.07 |   37170 |       |       |          |                                
#|*  2 |   HASH JOIN                   |              |      1 |      9 |     10 |00:00:00.07 |   37170 |  1452K|  1452K| 1424K (0)|                                
#|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |     10 |00:00:00.01 |   37164 |       |       |          |                                
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |     10 |00:00:00.01 |       6 |       |       |          |                                
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |      1 |     10 |     10 |00:00:00.01 |       4 |       |       |          |                                
#-----------------------------------------------------------------------------------------------------------------------------------                                
#
#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: 744086632          
#
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#|   0 | SELECT STATEMENT              |              |      1 |        |    500K|00:00:08.01 |     123K|  12985 |       |       |          |                       
#|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:08.01 |     123K|  12985 |       |       |          |                       
#|*  2 |   HASH JOIN                   |              |      1 |      9 |    500K|00:00:07.72 |     123K|  12985 |    15M|  3723K|   23M (0)|                       
#|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |    500K|00:00:00.11 |   37164 |      0 |       |       |          |                       
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |    500K|00:00:06.72 |   86064 |  12985 |       |       |          |                       
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |      1 |     10 |    500K|00:00:00.43 |   34211 |    634 |       |       |          |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#
#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 列(该 11.2.0.1 环境配置了更大的 PGA_AGGREGATE_TARGET)。我们修改OPTIMIZER_MODE,重新进行测试:

#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: 1340904442         
#
#--------------------------------------------------------------------------------------------------------                       
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                       
#--------------------------------------------------------------------------------------------------------                       
#|   0 | SELECT STATEMENT              |              |      1 |        |     10 |00:00:00.01 |   37189 |                       
#|*  1 |  FILTER                       |              |      1 |        |     10 |00:00:00.01 |   37189 |                       
#|   2 |   NESTED LOOPS                |              |      1 |        |     10 |00:00:00.01 |   37189 |                       
#|   3 |    NESTED LOOPS               |              |      1 |      2 |     10 |00:00:00.01 |   37179 |                       
#|*  4 |     TABLE ACCESS FULL         | T3           |      1 |      7 |     10 |00:00:00.01 |   37165 |                       
#|*  5 |     INDEX UNIQUE SCAN         | SYS_C0017307 |     10 |      1 |     10 |00:00:00.01 |      14 |                       
#|   6 |    TABLE ACCESS BY INDEX ROWID| T1           |     10 |      1 |     10 |00:00:00.01 |      10 |                       
#--------------------------------------------------------------------------------------------------------                       
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                     
#   1 - filter(:N1<=:N2)             
#   4 - 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 WHERE   T1.C1=T3.C1   AND T1.C1 BETWEEN :N1 AND :N2     
#
#Plan hash value: 1340904442         
#
#-----------------------------------------------------------------------------------------------------------------              
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |              
#-----------------------------------------------------------------------------------------------------------------              
#|   0 | SELECT STATEMENT              |              |      1 |        |    500K|00:00:03.77 |     782K|      1 |              
#|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:03.77 |     782K|      1 |              
#|   2 |   NESTED LOOPS                |              |      1 |        |    500K|00:00:03.52 |     782K|      1 |              
#|   3 |    NESTED LOOPS               |              |      1 |      2 |    500K|00:00:02.00 |     282K|      1 |              
#|*  4 |     TABLE ACCESS FULL         | T3           |      1 |      7 |    500K|00:00:00.39 |   70498 |      0 |              
#|*  5 |     INDEX UNIQUE SCAN         | SYS_C0017307 |    500K|      1 |    500K|00:00:00.95 |     212K|      1 |              
#|   6 |    TABLE ACCESS BY INDEX ROWID| T1           |    500K|      1 |    500K|00:00:00.86 |     500K|      0 |              
#-----------------------------------------------------------------------------------------------------------------              
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                     
#   1 - filter(:N1<=:N2)             
#   4 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))                      
#   5 - access("T1"."C1"="T3"."C1")  
#       filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))

当OPTIMIZER_MODE设置为FIRST_ROWS_1时,这条 SQL 执行耗时 3.77 秒;

设置为ALL_ROWS时耗时 8.01 秒(对比之前只返回 10 行数据时的 0.01 秒和 0.07 秒)。很明显,这是不是就意味着:如果要优化性能,我们就应该把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: 2134503202         
#
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#|   0 | SELECT STATEMENT              |              |      1 |        |     10 |00:00:00.01 |   37169 |  37128 |       |       |          |                       
#|*  1 |  FILTER                       |              |      1 |        |     10 |00:00:00.01 |   37169 |  37128 |       |       |          |                       
#|*  2 |   HASH JOIN                   |              |      1 |     10 |     10 |00:00:00.01 |   37169 |  37128 |   711K|   711K| 1116K (0)|                       
#|   3 |    TABLE ACCESS BY INDEX ROWID| T2           |      1 |     10 |     10 |00:00:00.01 |       4 |      1 |       |       |          |                       
#|*  4 |     INDEX RANGE SCAN          | SYS_C0017308 |      1 |     10 |     10 |00:00:00.01 |       3 |      0 |       |       |          |                       
#|*  5 |    TABLE ACCESS FULL          | T4           |      1 |   1855 |     10 |00:00:00.01 |   37165 |  37127 |       |       |          |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#
#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 (level=2)

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

并且在谓词信息标题下方有一条备注,说明使用了2 级动态采样。另外,计划中 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: 2134503202         
#
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#|   0 | SELECT STATEMENT              |              |      1 |        |     10 |00:00:00.01 |   37169 |  37129 |       |       |          |                       
#|*  1 |  FILTER                       |              |      1 |        |     10 |00:00:00.01 |   37169 |  37129 |       |       |          |                       
#|*  2 |   HASH JOIN                   |              |      1 |     10 |     10 |00:00:00.01 |   37169 |  37129 |   711K|   711K| 1147K (0)|                       
#|   3 |    TABLE ACCESS BY INDEX ROWID| T2           |      1 |     10 |     10 |00:00:00.01 |       4 |      1 |       |       |          |                       
#|*  4 |     INDEX RANGE SCAN          | SYS_C0017308 |      1 |     10 |     10 |00:00:00.01 |       3 |      0 |       |       |          |                       
#|*  5 |    TABLE ACCESS FULL          | T4           |      1 |   1855 |     10 |00:00:00.01 |   37165 |  37128 |       |       |          |                       
#--------------------------------------------------------------------------------------------------------------------------------------------                       
#
#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 (level=2)

好的,相比这条 SQL 访问 T1、T3 表时的计划,本次计划发生了一点变化。执行计划不再采用嵌套循环连接;

实际上,它选用的计划和OPTIMIZER_MODE设置为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: 1136565161         
#
#-------------------------------------------------------------------------------------------------------                        
#| Id  | Operation                    | Name         | Starts | A-Rows |   A-Time   | Buffers | Reads  |                        
#-------------------------------------------------------------------------------------------------------                        
#|   0 | SELECT STATEMENT             |              |      1 |     10 |00:00:00.03 |     386K|  39012 |                        
#|   1 |  NESTED LOOPS                |              |      1 |     10 |00:00:00.03 |     386K|  39012 |                        
#|   2 |   NESTED LOOPS               |              |      1 |     10 |00:00:00.03 |     386K|  39011 |                        
#|   3 |    TABLE ACCESS FULL         | T4           |      1 |   1000K|00:00:00.38 |   37165 |  37133 |                        
#|*  4 |    INDEX UNIQUE SCAN         | SYS_C0017308 |   1000K|     10 |00:00:02.30 |     348K|   1878 |                        
#|   5 |   TABLE ACCESS BY INDEX ROWID| T2           |     10 |     10 |00:00:00.01 |      10 |      1 |                        
#-------------------------------------------------------------------------------------------------------                        
#
#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 版本起,已经不再支持 RBO 基于规则的优化器。同时还可以看到,执行计划现在采用嵌套循环连接,不再出现 FILTER 过滤算子;全表扫描再次先和另一张表的索引做连接,

这和将OPTIMIZER_MODE设置为FIRST_ROWS_1查询访问 T1 与 T3 表时的表现一模一样。

假设我们闲得无聊,我们假定 C1 列存储的是信用卡号,接下来做一个实验:提取SQL_ID为cvq22z77c8fww的全部子游标,

同时查看语句初次硬解析时传入的绑定变量。生产环境务必管控该功能的访问权限,因为它有可能泄露敏感数据。

#SELECT
#  *
#FROM
#  TABLE(DBMS_XPLAN.DISPLAY_CURSOR('cvq22z77c8fww',NULL,'ALLSTATS LAST +PEEKED_BINDS'));
#
#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: 744086632          
#
#--------------------------------------------------------------------------------------------------------------------------------------------
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
#--------------------------------------------------------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT              |              |      1 |        |    500K|00:00:08.01 |     123K|  12985 |       |       |          |
#|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:08.01 |     123K|  12985 |       |       |          |
#|*  2 |   HASH JOIN                   |              |      1 |      9 |    500K|00:00:07.72 |     123K|  12985 |    15M|  3723K|   23M (0)|
#|*  3 |    TABLE ACCESS FULL          | T3           |      1 |     10 |    500K|00:00:00.11 |   37164 |      0 |       |       |          |
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |     10 |    500K|00:00:06.72 |   86064 |  12985 |       |       |          |
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |      1 |     10 |    500K|00:00:00.43 |   34211 |    634 |       |       |          |
#--------------------------------------------------------------------------------------------------------------------------------------------
#
#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: 1340904442         
#
#-----------------------------------------------------------------------------------------------------------------
#| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
#-----------------------------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT              |              |      1 |        |    500K|00:00:03.77 |     782K|      1 |
#|*  1 |  FILTER                       |              |      1 |        |    500K|00:00:03.77 |     782K|      1 |
#|   2 |   NESTED LOOPS                |              |      1 |        |    500K|00:00:03.52 |     782K|      1 |
#|   3 |    NESTED LOOPS               |              |      1 |      2 |    500K|00:00:02.00 |     282K|      1 |
#|*  4 |     TABLE ACCESS FULL         | T3           |      1 |      7 |    500K|00:00:00.39 |   70498 |      0 |
#|*  5 |     INDEX UNIQUE SCAN         | SYS_C0017307 |    500K|      1 |    500K|00:00:00.95 |     212K|      1 |
#|   6 |    TABLE ACCESS BY INDEX ROWID| T1           |    500K|      1 |    500K|00:00:00.86 |     500K|      0 |
#-----------------------------------------------------------------------------------------------------------------
#
#Peeked Binds (identified by position):
#--------------------------------------
#
#   1 - (NUMBER): 1                  
#   2 - (NUMBER): 10                 
#
#Predicate Information (identified by operation id):                     
#---------------------------------------------------                     
#   1 - filter(:N1<=:N2)             
#   4 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))                      
#   5 - access("T1"."C1"="T3"."C1")  
#       filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))

那么,如果我们想弄清楚子游标产生的原因该怎么做?可以执行类似下面的操作:

#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 USOOSLFEBPISTABDLTBIIRLIOEMUTNFAITDLDBPCSCPTMBMROPMFLPLAFLRLHPB
------------- ---------------- ---------------- ------------ ---------------------------------------------------------------
94bv1jwkzcc38 0000000257BD5498 0000000257E2E5B0            0 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
              0000000257BD5498 0000000257DC8F18            1 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNN
              0000000257BD5498 0000000257C8C3F8            2 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNN

上面的结果表明,子游标是因为优化器模式不匹配而创建的(没错,我们修改过 `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 0000000257E2E5B0          1          2         22          0 N1      
 0 0000000257E2E5B0          2          2         22          0 N2      
 1 0000000257DC8F18          1          2         22          0 N1      
 1 0000000257DC8F18          2          2         22          0 N2      
 2 0000000257C8C3F8          1          2         22          0 N1      
 2 0000000257C8C3F8          2          2         22          0 N2

上面的示例展示的是实际使用过的执行计划,它有可能和 explain plan生成的计划不一样。

那我们来看看EXPLAIN PLAN FOR语法。

#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: 985299768          
#
#---------------------------------------------------------------------------------------------
#| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#---------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT             |              |     9 |  2394 | 10219   (1)| 00:02:03 |
#|*  1 |  HASH JOIN                   |              |     9 |  2394 | 10219   (1)| 00:02:03 |
#|*  2 |   TABLE ACCESS FULL          | T3           |    10 |    50 | 10214   (1)| 00:02:03 |
#|   3 |   TABLE ACCESS BY INDEX ROWID| T1           |    10 |  2610 |     4   (0)| 00:00:01 |
#|*  4 |    INDEX RANGE SCAN          | SYS_C0017307 |    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: 744086632          
#
#----------------------------------------------------------------------------------------------
#| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#----------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT              |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  1 |  FILTER                       |              |       |       |            |          |
#|*  2 |   HASH JOIN                   |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 | 10219   (1)| 00:02:03 |
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   637K|   178   (0)| 00:00:03 |
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |  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 数据类型。成本值也有所上升,估算行数从 9 一下子涨到了 2499(没有发生绑定变量窥视)。

我们再用 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;
#
#Plan hash value: 744086632          
#
#----------------------------------------------------------------------------------------------
#| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#----------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT              |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  1 |  FILTER                       |              |       |       |            |          |
#|*  2 |   HASH JOIN                   |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 | 10219   (1)| 00:02:03 |
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   637K|   178   (0)| 00:00:03 |
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |  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 及更高版本里前面的示例一样,谓词信息部分依旧存在错误。我们再次展示运行时统计信息以及解释计划:

#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: 744086632          
#
#----------------------------------------------------------------------------------------------
#| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
#----------------------------------------------------------------------------------------------
#|   0 | SELECT STATEMENT              |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  1 |  FILTER                       |              |       |       |            |          |
#|*  2 |   HASH JOIN                   |              |  2499 |   649K| 10397   (1)| 00:02:05 |
#|*  3 |    TABLE ACCESS FULL          | T3           |  2500 | 12500 | 10219   (1)| 00:02:03 |
#|   4 |    TABLE ACCESS BY INDEX ROWID| T1           |  2500 |   637K|   178   (0)| 00:00:03 |
#|*  5 |     INDEX RANGE SCAN          | SYS_C0017307 |  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          
#      37170  consistent gets        
#       7760  physical reads         
#          0  redo size              
#        812  bytes sent via SQL*Net to client                           
#        523  bytes received via SQL*Net from client                     
#          2  SQL*Net roundtrips to/from client                          
#          0  sorts (memory)         
#          0  sorts (disk)           
#         10  rows processed

请注意,即便我们看到了真实的运行时统计信息,上面这份计划也未必是实际执行计划。这种情况容易让人困惑:我们拿到的是真实的执行统计,但配套的执行计划却只是一个粗略的预估。

接下来做另一项测试。本次我们配置 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';
如果我们查看跟踪文件里的输出内容,可能会看到类似下面这样的信息:
#user_id=68 user_name=TESTUSER module=SQL*Plus action=
#sql_id=3g3fc5qyju0j3 plan_hash_value=744086632 problem_type=3
#----- Current SQL Statement for this session (sql_id=3g3fc5qyju0j3) -----
#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
#sql_text_length=119
#sql=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
#----- Explain Plan Dump -----
#----- Plan Table -----
#
#============
#Plan Table
#============
#-----------------------------------------------------+-----------------------------------+
#| Id  | Operation                      | Name        | Rows  | Bytes | Cost  | Time      |
#-----------------------------------------------------+-----------------------------------+
#| 0   | SELECT STATEMENT               |             |       |       |   10K |           |
#| 1   |  FILTER                        |             |       |       |       |           |
#| 2   |   HASH JOIN                    |             |     9 |  2394 |   10K |  00:02:03 |
#| 3   |    TABLE ACCESS FULL           | T3          |    10 |    50 |   10K |  00:02:03 |
#| 4   |    TABLE ACCESS BY INDEX ROWID | T1          |    10 |  2610 |     4 |  00:00:01 |
#| 5   |     INDEX RANGE SCAN           | SYS_C0017307|    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     : 11.2.0.1
#  parse_schema   : TESTUSER
#
#  plan_hash      : 744086632
#  plan_hash_2    : 3461135235
#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('11.2.0.1')
#      DB_VERSION('11.2.0.1')
#      ALL_ROWS
#      OUTLINE_LEAF(@"SEL$1")
#      FULL(@"SEL$1" "T3"@"SEL$1")
#      INDEX_RS_ASC(@"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 state dump:
#Compilation Environment Dump
#optimizer_mode_hinted               = false
#optimizer_features_hinted           = 0.0.0
#parallel_execution_enabled          = true
#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           = 11.2.0.1
#_optimizer_search_limit             = 5
#cpu_count                           = 8
#active_instance_count               = 1
#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       = 8
#_optimizer_max_permutations         = 2000
#pga_aggregate_target                = 1843200 KB
#_pga_max_size                       = 368640 KB
#_query_rewrite_maxdisjunct          = 257
#_smm_auto_min_io_size               = 56 KB
#_smm_auto_max_io_size               = 248 KB
#_smm_min_size                       = 1024 KB
#_smm_max_size                       = 184320 KB
#_smm_px_max_size                    = 921600 KB
#_cpu_to_io                          = 0
#_optimizer_undo_cost_change         = 11.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
#_optimizer_null_aware_antijoin      = true
#_optimizer_extend_jppd_view_types   = 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    = true
#_gby_hash_aggregation_enabled       = true
#_globalindex_pnum_filter_enabled    = true
#_px_minus_intersect                 = true
#_fix_control_key                    = 0
#_force_slave_mapping_intra_part_loads = false
#_force_tmp_segment_loads            = false
#_query_mmvrewrite_maxpreds          = 10
#_query_mmvrewrite_maxintervals      = 5
#_query_mmvrewrite_maxinlists        = 5
#_query_mmvrewrite_maxdmaps          = 10
#_query_mmvrewrite_maxcmaps          = 20
#_query_mmvrewrite_maxregperm        = 512
#_query_mmvrewrite_maxmergedcmaps    = 50
#_query_mmvrewrite_maxqryinlistvals  = 500
#_disable_parallel_conventional_load = false
#_trace_virtual_columns              = false
#_replace_virtual_columns            = true
#_virtual_column_overload_allowed    = true
#_kdt_buffering                      = true
#_first_k_rows_dynamic_proration     = true
#_optimizer_sortmerge_join_inequality = true
#_optimizer_aw_stats_enabled         = true
#_bloom_pruning_enabled              = true
#result_cache_mode                   = MANUAL
#_px_ual_serial_input                = true
#_optimizer_skip_scan_guess          = false
#_enable_row_shipping                = true
#_row_shipping_threshold             = 80
#_row_shipping_explain               = false
#transaction_isolation_level         = read_commited
#_optimizer_distinct_elimination     = true
#_optimizer_multi_level_push_pred    = true
#_optimizer_group_by_placement       = true
#_optimizer_rownum_bind_default      = 10
#_enable_query_rewrite_on_remote_objs = true
#_optimizer_extended_cursor_sharing_rel = simple
#_optimizer_adaptive_cursor_sharing  = true
#_direct_path_insert_features        = 0
#_optimizer_improve_selectivity      = true
#optimizer_use_pending_statistics    = false
#_optimizer_enable_density_improvements = true
#_optimizer_aw_join_push_enabled     = true
#_optimizer_connect_by_combine_sw    = true
#_enable_pmo_ctas                    = 0
#_optimizer_native_full_outer_join   = force
#_bloom_predicate_enabled            = true
#_optimizer_enable_extended_stats    = true
#_is_lock_table_for_ddl_wait_lock    = 0
#_pivot_implementation_method        = choose
#optimizer_capture_sql_plan_baselines = false
#optimizer_use_sql_plan_baselines    = true
#_optimizer_star_trans_min_cost      = 0
#_optimizer_star_trans_min_ratio     = 0
#_with_subquery                      = OPTIMIZER
#_optimizer_fkr_index_cost_bias      = 10
#_optimizer_use_subheap              = true
#parallel_degree_policy              = manual
#parallel_degree                     = 0
#parallel_min_time_threshold         = 10
#_parallel_time_unit                 = 10
#_optimizer_or_expansion_subheap     = true
#_optimizer_free_transformation_heap = true
#_optimizer_reuse_cost_annotations   = true
#_result_cache_auto_size_threshold   = 100
#_result_cache_auto_time_threshold   = 1000
#_optimizer_nested_rollup_for_gset   = 100
#_nlj_batching_enabled               = 1
#parallel_query_default_dop          = 0
#is_recur_flags                      = 0
#optimizer_use_invisible_indexes     = false
#flashback_data_archive_internal_cursor = 0
#_optimizer_extended_stats_usage_control = 224
#_parallel_syspls_obey_force         = true
#cell_offload_processing             = true
#_rdbms_internal_fplib_enabled       = false
#db_file_multiblock_read_count       = 128
#_bloom_folding_enabled              = true
#_mv_generalized_oj_refresh_opt      = true
#cell_offload_compaction             = ADAPTIVE
#parallel_degree_limit               = 65535
#parallel_force_local                = false
#parallel_max_degree                 = 16
#total_cpu_count                     = 8
#cell_offload_plan_display           = AUTO
#_optimizer_coalesce_subqueries      = true
#_optimizer_fast_pred_transitivity   = true
#_optimizer_fast_access_pred_analysis = true
#_optimizer_unnest_disjunctive_subq  = true
#_optimizer_unnest_corr_set_subq     = true
#_optimizer_distinct_agg_transform   = true
#_aggregation_optimization_settings  = 0
#_optimizer_connect_by_elim_dups     = true
#_optimizer_eliminate_filtering_join = true
#_connect_by_use_union_all           = true
#dst_upgrade_insert_conv             = true
#advanced_queuing_internal_cursor    = 0
#_optimizer_unnest_all_subqueries    = true
#_bloom_predicate_pushdown_to_storage = true
#_bloom_vector_elements              = 0
#_bloom_pushing_max                  = 524288
#parallel_autodop                    = 0
#parallel_ddldml                     = 0
#_parallel_cluster_cache_policy      = adaptive
#_parallel_scalability               = 50
#iot_internal_cursor                 = 0
#_optimizer_instance_count           = 0
#_optimizer_connect_by_cb_whr_only   = false
#_suppress_scn_ch1k1_for_cqn           = nosuppress_1466
#_optimizer_join_factorization       = true
#_optimizer_use_cbqt_star_transformation = true
#_optimizer_table_expansion          = true
#_and_pruning_enabled                = true
#_deferred_constant_folding_mode     = DEFAULT
#_optimizer_distinct_placement       = true
#partition_pruning_internal_cursor   = 0
#parallel_hinted                     = none
#_sql_compatibility                  = 0
#_optimizer_use_feedback             = true
#_optimizer_try_st_before_jppd       = true
#Bug Fix Control Environment
#    fix  3834770 = 1      
#    fix  3746511 = enabled
#    fix  4519016 = enabled
#    fix  3118776 = enabled
#    fix  4488689 = enabled
#    fix  2194204 = disabled
#    fix  2660592 = enabled
#    fix  2320291 = enabled
#    fix  2324795 = enabled
#    fix  4308414 = enabled
#    fix  3499674 = disabled
#    fix  4569940 = enabled
#    fix  4631959 = enabled
#    fix  4519340 = enabled
#    fix  4550003 = enabled
#    fix  1403283 = enabled
#    fix  4554846 = enabled
#    fix  4602374 = enabled
#    fix  4584065 = enabled
#    fix  4545833 = enabled
#    fix  4611850 = enabled
#    fix  4663698 = enabled
#    fix  4663804 = enabled
#    fix  4666174 = enabled
#    fix  4567767 = enabled
#    fix  4556762 = 15     
#    fix  4728348 = enabled
#    fix  4708389 = enabled
#    fix  4175830 = enabled
#    fix  4752814 = enabled
#    fix  4583239 = enabled
#    fix  4386734 = enabled
#    fix  4887636 = enabled
#    fix  4483240 = enabled
#    fix  4872602 = disabled
#    fix  4711525 = enabled
#    fix  4545802 = enabled
#    fix  4605810 = enabled
#    fix  4704779 = enabled
#    fix  4900129 = enabled
#    fix  4924149 = enabled
#    fix  4663702 = enabled
#    fix  4878299 = enabled
#    fix  4658342 = enabled
#    fix  4881533 = enabled
#    fix  4676955 = enabled
#    fix  4273361 = enabled
#    fix  4967068 = enabled
#    fix  4969880 = disabled
#    fix  5005866 = enabled
#    fix  5015557 = enabled
#    fix  4705343 = enabled
#    fix  4904838 = enabled
#    fix  4716096 = enabled
#    fix  4483286 = disabled
#    fix  4722900 = enabled
#    fix  4615392 = enabled
#    fix  5096560 = enabled
#    fix  5029464 = enabled
#    fix  4134994 = enabled
#    fix  4904890 = enabled
#    fix  5104624 = enabled
#    fix  5014836 = enabled
#    fix  4768040 = enabled
#    fix  4600710 = enabled
#    fix  5129233 = enabled
#    fix  4595987 = enabled
#    fix  4908162 = enabled
#    fix  5139520 = enabled
#    fix  5084239 = enabled
#    fix  5143477 = disabled
#    fix  2663857 = enabled
#    fix  4717546 = enabled
#    fix  5240264 = disabled
#    fix  5099909 = enabled
#    fix  5240607 = enabled
#    fix  5195882 = enabled
#    fix  5220356 = enabled
#    fix  5263572 = enabled
#    fix  5385629 = enabled
#    fix  5302124 = enabled
#    fix  5391942 = enabled
#    fix  5384335 = enabled
#    fix  5482831 = enabled
#    fix  4158812 = enabled
#    fix  5387148 = enabled
#    fix  5383891 = enabled
#    fix  5466973 = enabled
#    fix  5396162 = enabled
#    fix  5394888 = enabled
#    fix  5395291 = enabled
#    fix  5236908 = enabled
#    fix  5509293 = enabled
#    fix  5449488 = enabled
#    fix  5567933 = enabled
#    fix  5570494 = enabled
#    fix  5288623 = enabled
#    fix  5505995 = enabled
#    fix  5505157 = enabled
#    fix  5112460 = enabled
#    fix  5554865 = enabled
#    fix  5112260 = enabled
#    fix  5112352 = enabled
#    fix  5547058 = enabled
#    fix  5618040 = enabled
#    fix  5585313 = enabled
#    fix  5547895 = enabled
#    fix  5634346 = enabled
#    fix  5620485 = enabled
#    fix  5483301 = enabled
#    fix  5657044 = enabled
#    fix  5694984 = enabled
#    fix  5868490 = enabled
#    fix  5650477 = enabled
#    fix  5611962 = enabled
#    fix  4279274 = enabled
#    fix  5741121 = enabled
#    fix  5714944 = enabled
#    fix  5391505 = enabled
#    fix  5762598 = enabled
#    fix  5578791 = enabled
#    fix  5259048 = enabled
#    fix  5882954 = enabled
#    fix  2492766 = enabled
#    fix  5707608 = enabled
#    fix  5891471 = enabled
#    fix  5884780 = enabled
#    fix  5680702 = enabled
#    fix  5371452 = enabled
#    fix  5838613 = enabled
#    fix  5949981 = enabled
#    fix  5624216 = enabled
#    fix  5741044 = enabled
#    fix  5976822 = enabled
#    fix  6006457 = enabled
#    fix  5872956 = enabled
#    fix  5923644 = enabled
#    fix  5943234 = enabled
#    fix  5844495 = enabled
#    fix  4168080 = enabled
#    fix  6020579 = enabled
#    fix  5842686 = disabled
#    fix  5996801 = enabled
#    fix  5593639 = enabled
#    fix  6133948 = enabled
#    fix  3151991 = enabled
#    fix  6146906 = enabled
#    fix  6239909 = enabled
#    fix  6267621 = enabled
#    fix  5909305 = enabled
#    fix  6279918 = enabled
#    fix  6141818 = enabled
#    fix  6151963 = enabled
#    fix  6251917 = enabled
#    fix  6282093 = enabled
#    fix  6119510 = enabled
#    fix  6119382 = enabled
#    fix  3801750 = enabled
#    fix  5705630 = disabled
#    fix  5944076 = enabled
#    fix  5406763 = enabled
#    fix  6070954 = enabled
#    fix  6282944 = enabled
#    fix  6138746 = enabled
#    fix  6082745 = enabled
#    fix  3426050 = enabled
#    fix   599680 = enabled
#    fix  6062266 = enabled
#    fix  6087237 = enabled
#    fix  6122894 = enabled
#    fix  6377505 = enabled
#    fix  5893768 = enabled
#    fix  6163564 = enabled
#    fix  6073325 = enabled
#    fix  6188881 = enabled
#    fix  6007259 = enabled
#    fix  6239971 = enabled
#    fix  5284200 = disabled
#    fix  6042205 = enabled
#    fix  6051211 = enabled
#    fix  6434668 = enabled
#    fix  6438752 = enabled
#    fix  5936366 = enabled
#    fix  6439032 = enabled
#    fix  6438892 = enabled
#    fix  6006300 = enabled
#    fix  5947231 = enabled
#    fix  5416118 = 1      
#    fix  6365442 = 1      
#    fix  6239039 = enabled
#    fix  6502845 = enabled
#    fix  6913094 = enabled
#    fix  6029469 = enabled
#    fix  5919513 = enabled
#    fix  6057611 = enabled
#    fix  6469667 = enabled
#    fix  6608941 = disabled
#    fix  6368066 = enabled
#    fix  6329318 = enabled
#    fix  6656356 = enabled
#    fix  4507997 = enabled
#    fix  6671155 = enabled
#    fix  6694548 = enabled
#    fix  6688200 = enabled
#    fix  6612471 = enabled
#    fix  6708183 = disabled
#    fix  6326934 = enabled
#    fix  6520717 = disabled
#    fix  6714199 = enabled
#    fix  6681545 = enabled
#    fix  6748058 = enabled
#    fix  6167716 = enabled
#    fix  6674254 = enabled
#    fix  6468287 = enabled
#    fix  6503543 = enabled
#    fix  6808773 = disabled
#    fix  6766962 = enabled
#    fix  6120483 = enabled
#    fix  6670551 = enabled
#    fix  6771838 = enabled
#    fix  6626018 = disabled
#    fix  6530596 = enabled
#    fix  6778642 = enabled
#    fix  6699059 = enabled
#    fix  6376551 = enabled
#    fix  6429113 = enabled
#    fix  6782437 = enabled
#    fix  6776808 = enabled
#    fix  6765823 = enabled
#    fix  6768660 = enabled
#    fix  6782665 = enabled
#    fix  6610822 = enabled
#    fix  6514189 = enabled
#    fix  6818410 = enabled
#    fix  6827696 = enabled
#    fix  6773613 = enabled
#    fix  5902962 = enabled
#    fix  6956212 = enabled
#    fix  3056297 = enabled
#    fix  6440977 = disabled
#    fix  6972291 = disabled
#    fix  6904146 = enabled
#    fix  6221403 = enabled
#    fix  5475051 = enabled
#    fix  6845871 = enabled
#    fix  5468809 = enabled
#    fix  6917633 = enabled
#    fix  4444536 = disabled
#    fix  6955210 = enabled
#    fix  6994194 = enabled
#    fix  6399597 = disabled
#    fix  6951776 = enabled
#    fix  5648287 = 3      
#    fix  6987082 = disabled
#    fix  7132036 = enabled
#    fix  6980350 = enabled
#    fix  5199213 = enabled
#    fix  7138405 = enabled
#    fix  7148689 = enabled
#    fix  6820988 = enabled
#    fix  7032684 = enabled
#    fix  6617866 = enabled
#    fix  7155968 = enabled
#    fix  7127980 = enabled
#    fix  6982954 = enabled
#    fix  7241819 = enabled
#    fix  6897034 = enabled
#    fix  7236148 = enabled
#    fix  7298570 = enabled
#    fix  7249095 = enabled
#    fix  7314499 = enabled
#    fix  7324224 = enabled
#    fix  7289023 = enabled
#    fix  7237571 = enabled
#    fix  7116357 = enabled
#    fix  7345484 = enabled
#    fix  7375179 = enabled
#    fix  6430500 = disabled
#    fix  5897486 = enabled
#    fix  6774209 = enabled
#    fix  7306637 = enabled
#    fix  6451322 = enabled
#    fix  7208131 = enabled
#    fix  7388652 = enabled
#    fix  7127530 = enabled
#    fix  6751206 = enabled
#    fix  6669103 = enabled
#    fix  7430474 = enabled
#    fix  6990305 = enabled
#    fix  7043307 = enabled
#    fix  6921505 = enabled
#    fix  7388457 = enabled
#    fix  3120429 = enabled
#    fix  7452823 = disabled
#    fix  6838105 = enabled
#    fix  6769711 = enabled
#    fix  7170213 = enabled
#    fix  6528872 = enabled
#    fix  7295298 = enabled
#    fix  5922070 = enabled
#    fix  7259468 = enabled
#    fix  6418552 = enabled
#    fix  4619997 = enabled
#    fix  7524366 = enabled
#    fix  6942476 = enabled
#    fix  6418771 = enabled
#    fix  7375077 = enabled
#    fix  5400639 = enabled
#    fix  4570921 = enabled
#    fix  7426911 = enabled
#    fix  5099019 = disabled
#    fix  7528216 = enabled
#    fix  7521266 = enabled
#    fix  7385140 = enabled
#    fix  7576516 = enabled
#    fix  7573526 = enabled
#    fix  7576476 = enabled
#    fix  7165898 = enabled
#    fix  7263214 = enabled
#    fix  3320140 = enabled
#    fix  7555510 = enabled
#    fix  7613118 = enabled
#    fix  7597059 = enabled
#    fix  7558911 = enabled
#    fix  5520732 = enabled
#    fix  7679490 = disabled
#    fix  7449971 = enabled
#    fix  3628118 = enabled
#    fix  4370840 = enabled
#    fix  7281191 = enabled
#    fix  7519687 = enabled
#    fix  5029592 = 3      
#    fix  6012093 = 1      
#    fix  6053861 = disabled
#    fix  6941515 = disabled
#    fix  7696414 = enabled
#    fix  7272039 = enabled
#    fix  7834811 = enabled
#    fix  7640597 = enabled
#    fix  7341616 = enabled
#    fix  7168184 = enabled
#    fix   399198 = enabled
#    fix  7831070 = enabled
#    fix  7676897 = disabled
#    fix  7414637 = enabled
#    fix  7585456 = enabled
#    fix  8202421 = enabled
#    fix  7658097 = disabled
#    fix  8251486 = enabled
#    fix  7132684 = enabled
#    fix  7512227 = enabled
#    fix  6972987 = enabled
#    fix  7199035 = enabled
#    fix  8243446 = enabled
#    fix  7650462 = enabled
#    fix  6720701 = enabled
#    fix  7592673 = enabled
#    fix  7718694 = enabled
#    fix  7534027 = enabled
#    fix  7708267 = enabled
#    fix  5716785 = enabled
#    fix  7356191 = enabled
#    fix  7679161 = enabled
#    fix  7597159 = enabled
#    fix  7499258 = enabled
#    fix  8328363 = enabled
#    fix  7452863 = enabled
#    fix  8284930 = enabled
#    fix  7298626 = enabled
#    fix  7657126 = enabled
#    fix  8371884 = enabled
#    fix  8318020 = enabled
#    fix  8255423 = enabled
#    fix  7135745 = enabled
#    fix  8356253 = enabled
#    fix  7534257 = enabled
#    fix  8323407 = enabled
#    fix  7539815 = enabled
#    fix  8289316 = enabled
#    fix  8447850 = enabled
#    fix  7675944 = enabled
#    fix  8355120 = enabled
#    fix  7176746 = enabled
#    fix  8442891 = enabled
#    fix  8373261 = enabled
#    fix  7679164 = enabled
#    fix  7670533 = enabled
#    fix  8408665 = enabled
#    fix  8491399 = enabled
#    fix  8348392 = enabled
#    fix  8348585 = enabled
#    fix  8508056 = enabled
#    fix  8335178 = enabled
#    fix  8515269 = enabled
#    fix  8247017 = enabled
#    fix  7325597 = enabled
#    fix  8531490 = enabled
#    fix  6163600 = enabled
#    fix  8589278 = disabled
#    fix  8557992 = enabled
#    fix  7556098 = enabled
#    fix  8580883 = enabled
#    fix  5892599 = disabled
#    fix  8609714 = enabled
#    fix  8514561 = enabled
#    fix  8619631 = disabled
#
#Query Block Registry:
#SEL$1 0x6e975eb0 (PARSER) [FINAL]
#
#:
#    call(in-use=25024, alloc=82024), compile(in-use=102048, alloc=164272), execution(in-use=12872, alloc=16200)
#
#End of Optimizer State Dump
#Dumping Hints
#=============

注意,从上面的内容里我们可以看到实际执行计划、被窥视的绑定变量、能够复现该执行计划的 Hint 集合,

还有大量常规以及隐藏的优化器参数。和 Oracle Database 10.2.0.2 的输出对比后你会发现,这份输出里列出了更多服务器参数。下面是4 级 10046 跟踪的示例(在 Oracle 11.1.0.6 及更高版本上默认行为已经改变:不再需要在目标 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 #6 len=118 dep=0 uid=68 oct=3 lid=68 tim=1264989049019258 hv=3172794915 ad='25b889600' sqlid='3g3fc5qyju0j3'
#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 #6:c=0,e=165,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=1264989049019257
#BINDS #6:
# 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=2b7f6eb34ea8  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=2b7f6eb34ec0  bln=22  avl=02  flg=01
#  value=10
#EXEC #6:c=999,e=1078,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=744086632,tim=1264989049020428
#FETCH #6:c=74988,e=75221,p=0,cr=37168,cu=0,mis=0,r=1,dep=0,og=1,plh=744086632,tim=1264989049095700
#FETCH #6:c=0,e=50,p=0,cr=2,cu=0,mis=0,r=9,dep=0,og=1,plh=744086632,tim=1264989049095954
#STAT #6 id=1 cnt=10 pid=0 pos=1 obj=0 op='FILTER  (cr=37170 pr=0 pw=0 time=0 us)'
#STAT #6 id=2 cnt=10 pid=1 pos=1 obj=0 op='HASH JOIN  (cr=37170 pr=0 pw=0 time=0 us cost=10219 size=2394 card=9)'
#STAT #6 id=3 cnt=10 pid=2 pos=1 obj=70288 op='TABLE ACCESS FULL T3 (cr=37164 pr=0 pw=0 time=0 us cost=10214 size=50 card=10)'
#STAT #6 id=4 cnt=10 pid=2 pos=2 obj=70284 op='TABLE ACCESS BY INDEX ROWID T1 (cr=6 pr=0 pw=0 time=0 us cost=4 size=2610 card=10)'
#STAT #6 id=5 cnt=10 pid=4 pos=1 obj=70285 op='INDEX RANGE SCAN SYS_C0017307 (cr=4 pr=0 pw=0 time=36 us cost=3 size=0 card=10)'
#CLOSE #6:c=0,e=10,dep=0,type=0,tim=1264989049096413
#=====================

如果你把上面的输出和 Oracle 10.2.0.2 的对比,会发现跟踪文件中会写入这条 SQL 的SQL_ID,STAT 行里包含基数估算信息,并且游标关闭时会在跟踪文件中新增一行记录。

当然,我们可以直接从 10046 跟踪文件里读取真实执行计划、绑定变量的值以及数据类型。不过这里我们改用 TKPROF 工具处理(使用 TKPROF 时将看不到绑定变量的值)。

> tkprof or112_ora_5062_10046_EXECUTION_PLAN.trc or112_ora_5062_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      0.07       0.07          0      37170          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.07       0.07          0      37170          0          10
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 68 
Rows     Row Source Operation
-------  ---------------------------------------------------
     10  FILTER  (cr=37170 pr=0 pw=0 time=0 us)
     10   HASH JOIN  (cr=37170 pr=0 pw=0 time=0 us cost=10219 size=2394 card=9)
     10    TABLE ACCESS FULL T3 (cr=37164 pr=0 pw=0 time=0 us cost=10214 size=50 card=10)
     10    TABLE ACCESS BY INDEX ROWID T1 (cr=6 pr=0 pw=0 time=0 us cost=4 size=2610 card=10)
     10     INDEX RANGE SCAN SYS_C0017307 (cr=4 pr=0 pw=0 time=36 us cost=3 size=0 card=10)(object id 70285)


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

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