在上一篇《第86期 Oracle执行计划,彻底搞懂Oracle的执行计划》中,我们学习了以下操作:
- 展示 SQL 语句**实际(TYPICAL 模式)**执行计划,包含基数预估与成本值。
- 通过 GATHER_PLAN_STATISTICS Hint,展示 SQL 语句**实际(ALLSTATS LAST 模式)**执行计划,用来对比预估基数和计划里每一步操作返回的真实行数,同时获取语句最终实际执行时间(中间步骤的耗时不一定贴近真实耗时)。
- 在会话级别将 STATISTICS_LEVEL 参数设置为 ALL,展示 SQL 语句**实际(ALLSTATS LAST 模式)**执行计划,可以拿到最终执行时间,并且中间步骤的耗时估算也更加贴近真实值(部分场景下,SQL 整体执行耗时会明显上升)。
- 在 ALL_ROWS、FIRST_ROWS_1、CHOOSE 之间切换 OPTIMIZER_MODE 参数,对比实际执行计划的变化。注意:将优化器模式设置为 FIRST_ROWS_1 时,优化器可能低估返回行数,执行计划中就能观察到这个现象。
- 使用 SQL*Plus 的 AUTOTRACE 和 EXPLAIN PLAN 展示一份看似正确的执行计划,并探究依赖这类计划时可能遇到的各类问题。
- 观察绑定变量窥视(bind peeking)对执行计划带来的影响。
- 通过设置 10132 事件来获取执行计划。
- 直接从 10046 跟踪文件中读取执行计划。
- 使用 TKPROF 工具处理跟踪文件,同时了解让 TKPROF 生成 Explain 计划时会引发的一个典型隐患。
那么还有哪些内容没有讲到呢?
今天重点研究如何使用 Statspack 采集执行计划。如果你翻阅 Oracle 官方文档,
很容易误以为 Statspack 在 10.1.0.1 版本后就被 AWR 报表取代、不再提供(AWR 需要额外付费授权,而 Statspack 免费)。
但 Statspack 在 Oracle 11.2.0.1 中依然可用,而且相比早期版本还有增强。
当 Statspack 快照级别为 6 或更高时,就会采集执行计划 —— 不一定能捕获所有 SQL 的执行计划(可以调整采集过滤条件),
默认配置下也能捕获相当多 SQL 的计划。
本文会复用上篇文章里创建的测试表,读取 Statspack 采集保存的执行计划。
我们需要打开两个 SQL*Plus 会话:
第一个以 PERFSTAT 用户(Statspack 数据的属主)登录;
第二个登录为上一篇文章里测试表的属主。
首先我们先看看如何安装 Statspack?
cd $ORACLE_HOME/rdbms/admin目录下。找到如下脚本。
1. spcreate.sql脚本,安装 Statspack,创建 PERFSTAT 用户、表、包(SYS 执行)Oracle
2. spauto.sql脚本,定时快照 Job 脚本,用 DBMS_JOB 定时自动采集 snapshot(文章里面 5 分钟快照就是改这个)
3. spreport.sql脚本,生成 Statspack 性能报告(两个快照之间)
4. sprepsql.sql脚本,单独生成某个 SQL 的 Statspack 明细报告(看 Statspack 里保存的执行计划!你当前系列文章重点)
5. sppurge.sql脚本,清理旧的 Statspack 快照数据
6. spdrop.sql脚本,卸载 Statspack,删除 PERFSTAT 整个方案
7. spdoc.txt脚本, Statspack 官方说明文档,必读
安装完毕后,使用方法
1. sysdba执行spcreate.sql安装,创建PERFSTAT用户。
2. 切换到perfstat用户
3. 执行spauto.sql创建定时任务(或者自己写自定义定时快照脚本,搭配 DBMS_LOCK.SLEEP这种,复现测试)
4. 快照采集完之后,执行spreport.sql执行sprepsql.sql查看报告和SQL 执行计划
开始我们的实验,使用PERFSTAT 用户会执行下面的脚本,每 5 分钟采集一次级别 7 的 Statspack 快照。
#BEGIN # STATSPACK.SNAP(i_snap_level=>7); # DBMS_LOCK.SLEEP(300); # STATSPACK.SNAP(i_snap_level=>7); # DBMS_LOCK.SLEEP(300); # STATSPACK.SNAP(i_snap_level=>7); # DBMS_LOCK.SLEEP(300); # STATSPACK.SNAP(i_snap_level=>7); #END; #/
在 PERFSTAT 用户运行上述脚本的同时,另一个会话会多次执行下面这段脚本(每次 Statspack 采集周期内至少执行 2 次):
#SPOOL BindTestPlans.txt # #SET AUTOTRACE OFF #ALTER SESSION SET STATISTICS_LEVEL='ALL'; # #ALTER SESSION SET OPTIMIZER_MODE='ALL_ROWS'; #ALTER SYSTEM FLUSH SHARED_POOL; # #VARIABLE N1 NUMBER #VARIABLE N2 NUMBER #EXEC :N1:=1 #EXEC :N2:=1 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=1000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=10000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1'; #VARIABLE N1 NUMBER #VARIABLE N2 NUMBER #EXEC :N1:=1 #EXEC :N2:=1 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=1000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=10000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_10'; #VARIABLE N1 NUMBER #VARIABLE N2 NUMBER #EXEC :N1:=1 #EXEC :N2:=1 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=1000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=10000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #ALTER SESSION SET OPTIMIZER_MODE='FIRST_ROWS_1000'; #VARIABLE N1 NUMBER #VARIABLE N2 NUMBER #EXEC :N1:=1 #EXEC :N2:=1 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=1000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=10000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #EXEC :N2:=100000 # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # T3.C1, # SUBSTR(T1.C2,1,10) T1_C2, # SUBSTR(T2.C2,1,10) T2_C2 #FROM # T3, # T2, # T1 #WHERE # T1.C1=T3.C1 # AND T1.C1=T2.C1 # AND T1.C1 BETWEEN :N1 AND :N2; # #SELECT # * #FROM # TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST')); # #SPOOL OFF
上面这个脚本会切换优化器模式,并使用不同的绑定变量值执行同一条 SQL 语句。采集若干个 Statspack 快照之后,
我们就可以用如下 SQL,查看快照里是否存在同一条 SQL 拥有多个不同执行计划的情况
(注意:最好指定一个 Statspack 快照区间,而不是查询全部快照)。
#SPOOL MultiplePlans.txt # #SELECT # SQL_ID, # COUNT(DISTINCT PLAN_HASH_VALUE) C #FROM # PERFSTAT.STATS$SQL_PLAN_USAGE #GROUP BY # SQL_ID #HAVING # COUNT(DISTINCT PLAN_HASH_VALUE)>1; # #SPOOL OFF
sql的执行结构如下:
SQL_ID C ------------- ---------- asc81p5ttbyf4 2 94bv1jwkzcc38 2 77z8bh7b87r1s 4 ga9j9xk5cy9s0 2 ddnbt67ftu9ds 2 0fr8zhn4ymu3v 2 cvn54b7yz0s8u 2 gjm43un5cy843 2 ff9zaqp3qfnah 2 gx4mv66pvj3xz 2 cvq22z77c8fww 3 a13qjgdbjcurr 2 1gu8t96d0bdmu 2 2x7j98pyk8ggm 2 8p3pacrbngv80 2 c6awqs517jpj0 2 cds8ys5zwkrpk 2 0u02w6ryqkg66 2 39m4sx9k63ba2 2 7ng34ruy5awxq 4 9dq71tc7vasgu 2 53saa2zkr6wc3 2 bsa0wjtftg3uw 2 cn5wth3738x0x 2 f3g84j69n0tjh 2 9nv4n7b54ph9a 2 0g5sb3zpqzss8 2
有意思:这里有几条 SQL,其中两条各存在 2 种执行计划、一条存在 3 种执行计划,还有两条各有 4 种执行计划。我们刚才的 SQL 没有限定只查询某个 Statspack 快照时间段,所以其中部分执行计划可能是很早之前采集的。我们来看其中一条 SQL 对应的执行计划
(注意:Statspack 快照不会采集谓词信息段和备注段,因此下面展示的执行计划会剔除这两部分内容)。
#SET LINESIZE 150 #SET PAGESIZE 10000 #SPOOL StatspackPlan.txt # #SELECT /*+ ORDERED */ # T.* #FROM # (SELECT DISTINCT # PLAN_HASH_VALUE # FROM # PERFSTAT.STATS$SQL_PLAN_USAGE # WHERE # SQL_ID='7ng34ruy5awxq' # ORDER BY # PLAN_HASH_VALUE) SPU, # TABLE(DBMS_XPLAN.DISPLAY( # 'PERFSTAT.STATS$SQL_PLAN', # NULL, # 'TYPICAL -PREDICATE -NOTE', # 'PLAN_HASH_VALUE='||SPU.PLAN_HASH_VALUE)) T; # #SPOOL OFF
执行sql后,执行结果如下:
#------------------------------------------------------------ #| Id | Operation | Name | #------------------------------------------------------------ #| 0 | SELECT STATEMENT | | #| 1 | MERGE JOIN OUTER | | #| 2 | SORT JOIN | | #| 3 | NESTED LOOPS OUTER | | #| 4 | TABLE ACCESS CLUSTER | IND$ | #| 5 | INDEX UNIQUE SCAN | I_OBJ# | #| 6 | TABLE ACCESS BY INDEX ROWID| IND_STATS$ | #| 7 | INDEX UNIQUE SCAN | I_IND_STATS$_OBJ# | #| 8 | SORT JOIN | | #| 9 | VIEW | | #| 10 | SORT GROUP BY | | #| 11 | TABLE ACCESS CLUSTER | CDEF$ | #| 12 | INDEX UNIQUE SCAN | I_COBJ# | #------------------------------------------------------------ # #---------------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | #---------------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | 7 (100)| | #| 1 | SORT ORDER BY | | 2 | 374 | 7 (43)| 00:00:01 | #| 2 | HASH JOIN OUTER | | 2 | 374 | 6 (34)| 00:00:01 | #| 3 | NESTED LOOPS OUTER | | 2 | 288 | 2 (0)| 00:00:01 | #| 4 | TABLE ACCESS CLUSTER | IND$ | 2 | 184 | 2 (0)| 00:00:01 | #| 5 | INDEX UNIQUE SCAN | I_OBJ# | 1 | | 1 (0)| 00:00:01 | #| 6 | TABLE ACCESS BY INDEX ROWID| IND_STATS$ | 1 | 52 | 0 (0)| | #| 7 | INDEX UNIQUE SCAN | I_IND_STATS$_OBJ# | 1 | | 0 (0)| | #| 8 | VIEW | | 1 | 43 | 3 (34)| 00:00:01 | #| 9 | SORT GROUP BY | | 1 | 15 | 3 (34)| 00:00:01 | #| 10 | TABLE ACCESS CLUSTER | CDEF$ | 1 | 15 | 2 (0)| 00:00:01 | #| 11 | INDEX UNIQUE SCAN | I_COBJ# | 1 | | 1 (0)| 00:00:01 | #---------------------------------------------------------------------------------------------------- # #---------------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | #---------------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | 5 (100)| | #| 1 | SORT GROUP BY | | 2 | 342 | 5 (20)| 00:00:01 | #| 2 | NESTED LOOPS OUTER | | 2 | 342 | 4 (0)| 00:00:01 | #| 3 | NESTED LOOPS OUTER | | 2 | 312 | 2 (0)| 00:00:01 | #| 4 | TABLE ACCESS CLUSTER | IND$ | 2 | 184 | 2 (0)| 00:00:01 | #| 5 | INDEX UNIQUE SCAN | I_OBJ# | 1 | | 1 (0)| 00:00:01 | #| 6 | TABLE ACCESS BY INDEX ROWID| IND_STATS$ | 1 | 64 | 0 (0)| | #| 7 | INDEX UNIQUE SCAN | I_IND_STATS$_OBJ# | 1 | | 0 (0)| | #| 8 | TABLE ACCESS CLUSTER | CDEF$ | 1 | 15 | 1 (0)| 00:00:01 | #| 9 | INDEX UNIQUE SCAN | I_COBJ# | 1 | | 0 (0)| | #---------------------------------------------------------------------------------------------------- # #----------------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | #----------------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | 7 (100)| | #| 1 | SORT ORDER BY | | 2 | 374 | 7 (43)| 00:00:01 | #| 2 | HASH JOIN OUTER | | 2 | 374 | 6 (34)| 00:00:01 | #| 3 | NESTED LOOPS OUTER | | 2 | 288 | 2 (0)| 00:00:01 | #| 4 | TABLE ACCESS CLUSTER | IND$ | 2 | 184 | 2 (0)| 00:00:01 | #| 5 | INDEX UNIQUE SCAN | I_OBJ# | 1 | | 1 (0)| 00:00:01 | #| 6 | TABLE ACCESS BY INDEX ROWID | IND_STATS$ | 1 | 52 | 0 (0)| | #| 7 | INDEX UNIQUE SCAN | I_IND_STATS$_OBJ# | 1 | | 0 (0)| | #| 8 | VIEW | | 1 | 43 | 3 (34)| 00:00:01 | #| 9 | SORT GROUP BY | | 1 | 15 | 3 (34)| 00:00:01 | #| 10 | TABLE ACCESS BY INDEX ROWID| CDEF$ | 1 | 15 | 2 (0)| 00:00:01 | #| 11 | INDEX RANGE SCAN | I_CDEF2 | 4 | | 1 (0)| 00:00:01 | #-----------------------------------------------------------------------------------------------------
好吧,这并不是我们刚才执行的那条 SQL 的执行计划。
但有意思的是,Oracle 的一条内部 SQL 居然生成了 4 种不同的执行计划
(其中一个计划看起来是在 RULE 优化器模式下生成的,依据就是**缺少 Cost(成本)列**)。
我们重新查询(说明:目标 SQL_ID 本来会在 DBMS_XPLAN 的输出中展示,但这里假设我们不知道这个 SQL_ID,也不想去查询 V$SQLAREA或V$SQL 视图):
#SET LINESIZE 150 #SET PAGESIZE 10000 #SPOOL StatspackPlan.txt # #SELECT /*+ ORDERED */ # T.* #FROM # (SELECT DISTINCT # PLAN_HASH_VALUE # FROM # PERFSTAT.STATS$SQL_PLAN_USAGE # WHERE # SQL_ID='77z8bh7b87r1s' # ORDER BY # PLAN_HASH_VALUE) SPU, # TABLE(DBMS_XPLAN.DISPLAY( # 'PERFSTAT.STATS$SQL_PLAN', # NULL, # 'TYPICAL -PREDICATE -NOTE', # 'PLAN_HASH_VALUE='||SPU.PLAN_HASH_VALUE)) T; # #SPOOL OFF
输入结果如下:
#----------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | #----------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | 1257 (100)| | #| 1 | FILTER | | | | | | #| 2 | HASH JOIN | | 13 | 8762 | 1257 (1)| 00:00:16 | #| 3 | TABLE ACCESS BY INDEX ROWID | T2 | 1000 | 138K| 4 (0)| 00:00:01 | #| 4 | INDEX RANGE SCAN | SYS_C0017308 | 1000 | | 3 (0)| 00:00:01 | #| 5 | NESTED LOOPS | | | | | | #| 6 | NESTED LOOPS | | 13 | 3458 | 1253 (1)| 00:00:16 | #| 7 | TABLE ACCESS FULL | T3 | 1000 | 5000 | 1227 (1)| 00:00:15 | #| 8 | INDEX UNIQUE SCAN | SYS_C0017307 | 1 | | 1 (0)| 00:00:01 | #| 9 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 261 | 2 (0)| 00:00:01 | #----------------------------------------------------------------------------------------------- # #-------------------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | #-------------------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | | 15215 (100)| | #| 1 | FILTER | | | | | | | #| 2 | HASH JOIN | | 32260 | 12M| | 15215 (1)| 00:03:03 | #| 3 | TABLE ACCESS FULL | T3 | 100K| 488K| | 10214 (1)| 00:02:03 | #| 4 | MERGE JOIN | | 32260 | 12M| | 4999 (1)| 00:01:00 | #| 5 | TABLE ACCESS BY INDEX ROWID | T1 | 100K| 24M| | 3897 (1)| 00:00:47 | #| 6 | INDEX RANGE SCAN | SYS_C0017307 | 100K| | | 191 (1)| 00:00:03 | #| 7 | SORT JOIN | | 32260 | 4473K| 9752K| 1103 (1)| 00:00:14 | #| 8 | TABLE ACCESS BY INDEX ROWID| T2 | 32260 | 4473K| | 80 (0)| 00:00:01 | #| 9 | INDEX RANGE SCAN | SYS_C0017308 | 32260 | | | 56 (0)| 00:00:01 | #-------------------------------------------------------------------------------------------------------- # #------------------------------------------------------------------------------------------------ #| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | #------------------------------------------------------------------------------------------------ #| 0 | SELECT STATEMENT | | | | 10217 (100)| | #| 1 | FILTER | | | | | | #| 2 | NESTED LOOPS | | | | | | #| 3 | NESTED LOOPS | | 12 | 4896 | 10217 (1)| 00:02:03 | #| 4 | NESTED LOOPS | | 12 | 3192 | 10216 (1)| 00:02:03 | #| 5 | TABLE ACCESS FULL | T3 | 100K| 488K| 10214 (1)| 00:02:03 | #| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 261 | 2 (0)| 00:00:01 | #| 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 1 | | 1 (0)| 00:00:01 | #| 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 1 | | 0 (0)| | #| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 142 | 1 (0)| 00:00:01 | #------------------------------------------------------------------------------------------------ # #------------------------------------------------------------------------------------------------------- #| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | #------------------------------------------------------------------------------------------------------- #| 0 | SELECT STATEMENT | | | | | 10897 (100)| | #| 1 | FILTER | | | | | | | #| 2 | HASH JOIN | | 9998 | 3983K| | 10897 (1)| 00:02:11 | #| 3 | TABLE ACCESS FULL | T3 | 10000 | 50000 | | 10214 (1)| 00:02:03 | #| 4 | HASH JOIN | | 9999 | 3935K| 2432K| 682 (1)| 00:00:09 | #| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 16130 | 2236K| | 41 (0)| 00:00:01 | #| 6 | INDEX RANGE SCAN | SYS_C0017308 | 16130 | | | 29 (0)| 00:00:01 | #| 7 | TABLE ACCESS BY INDEX ROWID| T1 | 10000 | 2548K| | 392 (0)| 00:00:05 | #| 8 | INDEX RANGE SCAN | SYS_C0017307 | 10000 | | | 21 (0)| 00:00:01 | #-------------------------------------------------------------------------------------------------------
这看起来就是我们这条查询对应的执行计划了,但这条查询真的只有 4 种不同的执行计划吗(按理说自适应游标共享 ACS 应该已经生效)?
实际上,数据库创建了超过 4 个子游标,但**去重之后只有 4 套不一样的执行计划**,这点很有意思。
上面这几组计划(从上往下看)的连接方式分别是:
嵌套循环(一张表和另一张表的索引之间)、嵌套循环、哈希连接
排序合并连接、哈希连接
嵌套循环、嵌套循环、嵌套循环
哈希连接、哈希连接
Nested Loops (between a table and an index for another table), Nested Loops, Hash Join
Sort – Merge Join, Hash Join
Nested Loops, Nested Loops, Nested Loops
Hash Join, Hash Join
基于成本的优化器(CBO)确实很巧妙。
Statspack 能够采集到执行计划固然很棒,但因为缺少谓词信息,计划的参考价值会打折扣。
接下来我们看脚本运行期间抓取到的 DBMS_XPLAN 输出,只在生成新执行计划(或者新子游标)的时候才打印计划:
#SQL_ID 77z8bh7b87r1s, child number 0
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.08 | 37173 |
#|* 1 | FILTER | | 1 | | 1 |00:00:00.08 | 37173 |
#| 2 | NESTED LOOPS | | 1 | | 1 |00:00:00.08 | 37173 |
#| 3 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.08 | 37172 |
#| 4 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.08 | 37169 |
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 1 | 1 |00:00:00.08 | 37165 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 |00:00:00.01 | 4 |
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 1 | 1 | 1 |00:00:00.01 | 3 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 1 | 1 | 1 |00:00:00.01 | 3 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 1 | 1 |00:00:00.01 | 1 |
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 1
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 2222254696
#
#------------------------------------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
#------------------------------------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 1000 |00:00:00.08 | 37382 | | | |
#|* 1 | FILTER | | 1 | | 1000 |00:00:00.08 | 37382 | | | |
#|* 2 | HASH JOIN | | 1 | 998 | 1000 |00:00:00.08 | 37382 | 1452K| 1452K| 1476K (0)|
#|* 3 | TABLE ACCESS FULL | T3 | 1 | 1000 | 1000 |00:00:00.07 | 37164 | | | |
#|* 4 | HASH JOIN | | 1 | 999 | 1000 |00:00:00.01 | 218 | 830K| 830K| 1277K (0)|
#| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1000 | 1000 |00:00:00.01 | 42 | | | |
#|* 6 | INDEX RANGE SCAN | SYS_C0017308 | 1 | 1000 | 1000 |00:00:00.01 | 4 | | | |
#| 7 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1000 | 1000 |00:00:00.01 | 176 | | | |
#|* 8 | INDEX RANGE SCAN | SYS_C0017307 | 1 | 1000 | 1000 |00:00:00.01 | 71 | | | |
#------------------------------------------------------------------------------------------------------------------------------------
#
#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))
# 4 - access("T1"."C1"="T2"."C1")
# 6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)
# 8 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 2
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 2222254696
#
#------------------------------------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
#------------------------------------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 10000 |00:00:00.14 | 39279 | | | |
#|* 1 | FILTER | | 1 | | 10000 |00:00:00.14 | 39279 | | | |
#|* 2 | HASH JOIN | | 1 | 9998 | 10000 |00:00:00.14 | 39279 | 1452K| 1452K| 1795K (0)|
#|* 3 | TABLE ACCESS FULL | T3 | 1 | 10000 | 10000 |00:00:00.08 | 37164 | | | |
#|* 4 | HASH JOIN | | 1 | 9999 | 10000 |00:00:00.04 | 2115 | 3436K| 948K| 3949K (0)|
#| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 16130 | 10000 |00:00:00.01 | 391 | | | |
#|* 6 | INDEX RANGE SCAN | SYS_C0017308 | 1 | 16130 | 10000 |00:00:00.01 | 20 | | | |
#| 7 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 10000 | 10000 |00:00:00.01 | 1724 | | | |
#|* 8 | INDEX RANGE SCAN | SYS_C0017307 | 1 | 10000 | 10000 |00:00:00.01 | 686 | | | |
#------------------------------------------------------------------------------------------------------------------------------------
#
#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))
# 4 - access("T1"."C1"="T2"."C1")
# 6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)
# 8 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 3
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 736968136
#
#-------------------------------------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
#-------------------------------------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 100K|00:00:01.02 | 58272 | | | |
#|* 1 | FILTER | | 1 | | 100K|00:00:01.02 | 58272 | | | |
#|* 2 | HASH JOIN | | 1 | 32260 | 100K|00:00:00.97 | 58272 | 3838K| 1862K| 5510K (0)|
#|* 3 | TABLE ACCESS FULL | T3 | 1 | 100K| 100K|00:00:00.09 | 37164 | | | |
#| 4 | MERGE JOIN | | 1 | 32260 | 100K|00:00:00.68 | 21108 | | | |
#| 5 | TABLE ACCESS BY INDEX ROWID | T1 | 1 | 100K| 100K|00:00:00.22 | 17215 | | | |
#|* 6 | INDEX RANGE SCAN | SYS_C0017307 | 1 | 100K| 100K|00:00:00.06 | 6844 | | | |
#|* 7 | SORT JOIN | | 100K| 32260 | 100K|00:00:00.29 | 3893 | 30M| 1975K| 26M (0)|
#| 8 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 32260 | 100K|00:00:00.06 | 3893 | | | |
#|* 9 | INDEX RANGE SCAN | SYS_C0017308 | 1 | 32260 | 100K|00:00:00.02 | 189 | | | |
#-------------------------------------------------------------------------------------------------------------------------------------
#
#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))
# 6 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)
# 7 - access("T1"."C1"="T2"."C1")
# filter("T1"."C1"="T2"."C1")
# 9 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 4
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 100 |00:00:00.07 | 37411 |
#|* 1 | FILTER | | 1 | | 100 |00:00:00.07 | 37411 |
#| 2 | NESTED LOOPS | | 1 | | 100 |00:00:00.07 | 37411 |
#| 3 | NESTED LOOPS | | 1 | 1 | 100 |00:00:00.07 | 37311 |
#| 4 | NESTED LOOPS | | 1 | 1 | 100 |00:00:00.07 | 37291 |
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 1 | 100 |00:00:00.07 | 37171 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 100 | 1 | 100 |00:00:00.01 | 120 |
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 100 | 1 | 100 |00:00:00.01 | 20 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 100 | 1 | 100 |00:00:00.01 | 20 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 100 | 1 | 100 |00:00:00.01 | 100 |
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 5
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 1000 |00:00:00.08 | 39469 |
#|* 1 | FILTER | | 1 | | 1000 |00:00:00.08 | 39469 |
#| 2 | NESTED LOOPS | | 1 | | 1000 |00:00:00.08 | 39469 |
#| 3 | NESTED LOOPS | | 1 | 4 | 1000 |00:00:00.08 | 38469 |
#| 4 | NESTED LOOPS | | 1 | 4 | 1000 |00:00:00.08 | 38350 |
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 1000 | 1000 |00:00:00.08 | 37231 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 1000 | 1 | 1000 |00:00:00.01 | 1119 |
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 1000 | 1 | 1000 |00:00:00.01 | 119 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 1000 | 1 | 1000 |00:00:00.01 | 119 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 1000 | 1 | 1000 |00:00:00.01 | 1000 |
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 6
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 10000 |00:00:00.21 | 63553 |
#|* 1 | FILTER | | 1 | | 10000 |00:00:00.21 | 63553 |
#| 2 | NESTED LOOPS | | 1 | | 10000 |00:00:00.20 | 63553 |
#| 3 | NESTED LOOPS | | 1 | 3 | 10000 |00:00:00.17 | 53553 |
#| 4 | NESTED LOOPS | | 1 | 3 | 10000 |00:00:00.14 | 50692 |
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 6668 | 10000 |00:00:00.09 | 37831 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 10000 | 1 | 10000 |00:00:00.04 | 12861 |
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 10000 | 1 | 10000 |00:00:00.02 | 2861 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 10000 | 1 | 10000 |00:00:00.02 | 2861 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 10000 | 1 | 10000 |00:00:00.02 | 10000 |
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 7
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 100K|00:00:01.45 | 316K|
#|* 1 | FILTER | | 1 | | 100K|00:00:01.45 | 316K|
#| 2 | NESTED LOOPS | | 1 | | 100K|00:00:01.39 | 316K|
#| 3 | NESTED LOOPS | | 1 | 3 | 100K|00:00:01.07 | 216K|
#| 4 | NESTED LOOPS | | 1 | 9 | 100K|00:00:00.76 | 180K|
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 100K| 100K|00:00:00.16 | 43822 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 100K| 1 | 100K|00:00:00.46 | 136K|
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 100K| 1 | 100K|00:00:00.20 | 36565 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 100K| 1 | 100K|00:00:00.17 | 36565 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 100K| 1 | 100K|00:00:00.18 | 100K|
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 8
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 1056058413
#
#----------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
#----------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.07 | 37173 |
#|* 1 | FILTER | | 1 | | 1 |00:00:00.07 | 37173 |
#| 2 | NESTED LOOPS | | 1 | | 1 |00:00:00.07 | 37173 |
#| 3 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.07 | 37172 |
#| 4 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.07 | 37169 |
#|* 5 | TABLE ACCESS FULL | T3 | 1 | 1 | 1 |00:00:00.07 | 37165 |
#| 6 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 |00:00:00.01 | 4 |
#|* 7 | INDEX UNIQUE SCAN | SYS_C0017307 | 1 | 1 | 1 |00:00:00.01 | 3 |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017308 | 1 | 1 | 1 |00:00:00.01 | 3 |
#| 9 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 1 | 1 |00:00:00.01 | 1 |
#----------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 5 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 7 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
# 8 - access("T1"."C1"="T2"."C1")
# filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
#
#-
#
#SQL_ID 77z8bh7b87r1s, child number 9
#-------------------------------------
#SELECT T3.C1, SUBSTR(T1.C2,1,10) T1_C2, SUBSTR(T2.C2,1,10) T2_C2
#FROM T3, T2, T1 WHERE T1.C1=T3.C1 AND T1.C1=T2.C1 AND T1.C1
#BETWEEN :N1 AND :N2
#
#Plan hash value: 300618627
#
#------------------------------------------------------------------------------------------------------------------------------------
#| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
#------------------------------------------------------------------------------------------------------------------------------------
#| 0 | SELECT STATEMENT | | 1 | | 100 |00:00:00.07 | 37298 | | | |
#|* 1 | FILTER | | 1 | | 100 |00:00:00.07 | 37298 | | | |
#|* 2 | HASH JOIN | | 1 | 13 | 100 |00:00:00.07 | 37298 | 711K| 711K| 1272K (0)|
#| 3 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 100 | 100 |00:00:00.01 | 7 | | | |
#|* 4 | INDEX RANGE SCAN | SYS_C0017308 | 1 | 100 | 100 |00:00:00.01 | 3 | | | |
#| 5 | NESTED LOOPS | | 1 | | 100 |00:00:00.07 | 37291 | | | |
#| 6 | NESTED LOOPS | | 1 | 13 | 100 |00:00:00.07 | 37191 | | | |
#|* 7 | TABLE ACCESS FULL | T3 | 1 | 100 | 100 |00:00:00.07 | 37171 | | | |
#|* 8 | INDEX UNIQUE SCAN | SYS_C0017307 | 100 | 1 | 100 |00:00:00.01 | 20 | | | |
#| 9 | TABLE ACCESS BY INDEX ROWID| T1 | 100 | 1 | 100 |00:00:00.01 | 100 | | | |
#------------------------------------------------------------------------------------------------------------------------------------
#
#Predicate Information (identified by operation id):
#---------------------------------------------------
# 1 - filter(:N1<=:N2)
# 2 - access("T1"."C1"="T2"."C1")
# 4 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)
# 7 - filter(("T3"."C1"<=:N2 AND "T3"."C1">=:N1))
# 8 - access("T1"."C1"="T3"."C1")
# filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))
#
#Note
#-----
# - dynamic sampling used for this statement (level=2)
注意看【备注】部分,里面说明执行了**2 级动态采样**,但并没有标注该采样仅针对 T2 表(原因:T2 表当时没有收集统计信息)。
我们可以去查看这个 SQL_ID 对应的 `V$SQL_SHARED_CURSOR` 视图,会很有收获。
该视图中的字段记录了创建子游标的原因。下面是执行 `DESC V$SQL_SHARED_CURSOR` 得到的字段列表:
Name ------------------------------ UNBOUND_CURSOR SQL_TYPE_MISMATCH OPTIMIZER_MISMATCH OUTLINE_MISMATCH STATS_ROW_MISMATCH LITERAL_MISMATCH FORCE_HARD_PARSE EXPLAIN_PLAN_CURSOR BUFFERED_DML_MISMATCH PDML_ENV_MISMATCH INST_DRTLD_MISMATCH SLAVE_QC_MISMATCH TYPECHECK_MISMATCH AUTH_CHECK_MISMATCH BIND_MISMATCH DESCRIBE_MISMATCH LANGUAGE_MISMATCH TRANSLATION_MISMATCH BIND_EQUIV_FAILURE INSUFF_PRIVS INSUFF_PRIVS_REM REMOTE_TRANS_MISMATCH LOGMINER_SESSION_MISMATCH INCOMP_LTRL_MISMATCH OVERLAP_TIME_MISMATCH EDITION_MISMATCH MV_QUERY_GEN_MISMATCH USER_BIND_PEEK_MISMATCH TYPCEHEK_DEP_MISMATCH NO_TRIGGER_MISMATCH FLASHBACK_CURSOR ANYDATA_TRANSFORMATION INCOMPLETE_CURSOR TOP_LEVEL_RPI_CURSOR DIFFERENT_LONG_LENGTH LOGICAL_STANDBY_APPLY DIFF_CALL_DURN BIND_UACS_DIFF PLSQL_CMP_SWITCHS_DIFF CURSOR_PARTS_MISMATCH STB_OBJECT_MISMATCH CROSSEDITION_TRIGGER_MISMATCH PQ_SLAVE_MISMATCH TOP_LEVEL_DDL_MISMATCH MULTI_PX_MISMATCH BIND_PEEKED_PQ_MISMATCH MV_REWRITE_MISMATCH ROLL_INVALID_MISMATCH OPTIMIZER_MODE_MISMATCH PX_MISMATCH MV_STALEOBJ_MISMATCH FLASHBACK_TABLE_MISMATCH LITREP_COMP_MISMATCH PLSQL_DEBUG LOAD_OPTIMIZER_STATS ACL_MISMATCH FLASHBACK_ARCHIVE_MISMATCH LOCK_USER_SCHEMA_FAILED REMOTE_MAPPING_MISMATCH LOAD_RUNTIME_HEAP_FAILED HASH_MATCH_FAILED PURGED_CURSOR BIND_LENGTH_UPGRADEABLE
Oracle Database 11.2.0.1 确实内置了非常多会触发新建子游标的条件。那么,
在我们这个测试里,生成这些子游标的原因是什么呢(至少是脚本多次执行后,还保留在库缓存中的那些子游标)?
CN USOOSLFEBPISTABDLTBIIRLIOEMUTNFAITDLDBPCSCPTMBMROPMFLPLAFLRLHPB -- --------------------------------------------------------------- 0 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNNNNNN 1 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 2 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 3 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 4 NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNYNNNNNYNNNNNNNN 5 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 6 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 7 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 8 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN 9 NNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
从上面结果可以看出,产生子游标的原因是:**L(LOAD_OPTIMIZER_STATS,加载优化器统计信息)**、**B(BIND_EQUIV_FAILURE,绑定变量等价性校验失败)**、**O(OPTIMIZER_MODE_MISMATCH,优化器模式不匹配)**。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12798004/viewspace-3187021/ ,如需转载,请注明出处,否则将追究法律责任。