首页 > 代码库 > 如何得到真实的执行计划
如何得到真实的执行计划
通常,我们可以使用如下四种方法来得到目标sql的执行计划:
(1)explain plan命令
(2)dbms_xplan包
(3)sqlplus中的autotrace开关
(4)10046事件
这其中除了第四种方法之外,其他三种方法得到的执行计划都可能是不准确的。在oracle数据库中判断得到的执行计划是否准确,就是看目标sql是否被真正执行,真正执行过的sql所对应的执行计划就是准确的,反之则可能不准。注意,这里判断原则从严格意义上来说并不适用于autotrace开关,因为所有使用autotrace开关所显示的执行计划都可能是不准的,即使对应的目标sql实际上上已经执行过。
下面我们就用上述原则来判断除了第4种以外的其他三种方法中哪些方法得到的执行计划是准的,哪些方法得到的执行计划可能不准。
对使用第一种方法(explain plan)得到的执行计划而言,因为此时目标sql并没有被实际执行,所以用该方法得到的执行计划有可能是不准的,尤其在目标sql包含绑定变量的时候。在默认开启绑定变量窥探(bind peeking)的情况下,对含绑定变量的目标sql使用explain plan得到执行计划只是一个半成品,oracle在随后对该sql的绑定变量进行窥探后就得到了这些绑定变量具体的值,此时oracle很可能会随上述半成品的执行计划做调整,一旦做了调整,使用explain plan命令得到的执行计划就不准了。
对于使用第二种方法,针对不同的应用场景,你可以选择如下四种方式中的一种:
select * from table(dbms_xplan.display)
select * from table(dbms_xplan.display_cursor(null,null,‘advanced‘)
select * from table(dbms_xplan.display_cursor(‘sql__id/hash_value‘,child_cursor_number,‘advanced‘));
select * from table(dbms_xplan.display_awr(‘sql_id‘));
显然,执行 select * from table(dbms_xplan.display)所得到的执行计划可能是不准确的,因为它只是拥有查看使用explain plan命令得到的目标sql的执行计划,目标sql此时还没有被真正执行,所以用它得到的执行计划可能是不准的。使用剩下的三种方式所得到的执行计划都是准的,因为此时目标sql都已经被实际执行过了。
对于使用第三种方法(sqlplus中的autotrace开关)而言,你可以选择执行如下三种方式中一种来开启autotrace开关
set autotrace on(set antot on)
set autotrace traceonly(set autot trace)
set autotrace traceonly explain(set autot trace exp)
上述三种方式中,当使用set autotrace on和set autotrace traceonly时,目标sql都已经被实际执行过了,正是因为被实际执行过了,所以set autotrace on和set autotrace traceonly的情况下我们能看到目标sql的实际资源消耗情况。当使用set autotrace traceonly explain是,如果执行时select语句,则该select语句并没有被oracle实际执行,但如果执行的是DML语句,情况就不一样了,此时的DML语句会被实际oracle实际执行的。
我们现在来证明上述关于set autotrace traceonly explain的观点。先正常执行一次如下sql:
SQL> select count(*) from emp where ename=‘JAMES‘;
COUNT(*)
---------- 1
从如下查询结果中可以看到上述sql所对应的executions的值为1,这说明oracle刚才确实执行了一次上述sql
SQL> select sql_text,executions from v$sqlarea where sql_text like ‘select count(*) from emp%‘;
SQL_TEXT EXECUTIONS
-------------------------------------------------------------------------------- ----------
select count(*) from emp where ename=‘JAMES‘ 1
现在清空shared pool
SQL> alter system flush shared_pool;
System altered.
从如下查询结果中可以看到上述sql所对应的shared cursor现在已经不在shared pool里了
SQL> select sql_text,executions from v$sqlarea where sql_text like ‘select count(*) from emp%‘;
no rows selected
在当前session中已traceonly explain方式打开autotrace后执行上述sql
SQL> set autotrace traceonly explain;
SQL> select count(*) from scott.emp where ename=‘JAMES‘
2 ;
Execution Plan
----------------------------------------------------------
Plan hash value: 2083865914
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS FULL| EMP | 1 | 6 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("ENAME"=‘JAMES‘)
我们再次查询v$sqlare
SQL> select sql_text,executions from v$sqlarea where sql_text like ‘select count(*) from scott.emp%‘;
SQL_TEXT EXECUTIONS
-------------------------------------------------------------------------------- ----------
select count(*) from scott.emp where ename=‘JAMES‘ 0
从上述查询结果中可以看到该select 语句所对应的EXECUTIONS为0,这说明oracle刚才确实只解析了该select句但并没有实际执行它们。证明上述观点(当使用set autot trace exp时,如果执行的是select语句,则该select语句并没有被oracle实际执行)
接着,在当前session中执行如下DML语句:
SQL> delete from scott.emp where ename=‘JAMES‘;
1 row deleted.
Execution Plan
----------------------------------------------------------
Plan hash value: 161811703
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | DELETE STATEMENT | | 1 | 13 | 3 (0)| 00:00:01 |
| 1 | DELETE | EMP | | | | |
|* 2 | TABLE ACCESS FULL| EMP | 1 | 13 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("ENAME"=‘JAMES‘)
从查询结果可以看到,上述DML语句已经被真正执行了:
SQL> select count(*) from scott.emp where ename=‘JAMES‘;
COUNT(*)
----------
0
SQL> select sql_text,executions from v$sqlarea where sql_text like ‘delete from scott.emp%‘;
SQL_TEXT EXECUTIONS
-------------------------------------------------------------------------------- ----------
delete from scott.emp where ename=‘JAMES‘ 1
从上述实例中我们可以看出使用set autotrace traceonly explain后执行DML语句,该DML语句确实是会被oracle实际执行的,所以在使用set autotrace on,set autotrace traceonly 和set autotrace traceonly explain来获得DML语句的执行计划时要小心,因为这些DML语句实际上已经被执行了。
这里需要特别说明的是,虽然使用set autot 命令后目标sql实际上已经执行过了,但所有使用set autotrace命令(包括 set autotrace on,set autotrace traceonly,set autotrace traceonly explain)所得到的执行计划都可能是不准的,因为使用set autotrace命令所显示的执行计划都是来源于调用explain plan命令。
我们来看一个使用explain plan命令和set autotrace命令后得到的执行计划并不是目标sql真实执行计划的实例。创建一个测试表T1并插入一些数据:
SQL> create table t1 as select * from dba_objects;
Table created.
SQL> insert into t1 select * from t1;
87205 rows created.
SQL> commit;
Commit complete.
现在表T1的数据量是17万多条
SQL> select count(*) from t1;
COUNT(*)
----------
174410
在表T1的列object_id上创建一个单键值的B树索引IDX_T1
SQL> create index idx_t1 on t1(object_id);
Index created.
对表T1收集一个统计信息
SQL> exec dbms_stats.gather_table_stats(ownname=>‘SYS‘,tabname=>‘T1‘,estimate_percent=>100,cascade=>true);
PL/SQL procedure successfully completed.
创建两个绑定变量x和y,分别对他们赋值0和100000
SQL> var x number;
SQL> var y number;
SQL> exec :x=0;
SQL> exec :x:=0;
PL/SQL procedure successfully completed.
SQL> exec :y:=100000;
PL/SQL procedure successfully completed.
用explain plan产生以下sql的执行计划:
SQL> explain plan for select count(*) from t1 where object_id between :x and :y;
Explained.
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
Plan hash value: 2351893609
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | FILTER | | | | | |
|* 3 | INDEX RANGE SCAN| IDX_T1 | 436 | 2180 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(TO_NUMBER(:Y)>=TO_NUMBER(:X))
3 - access("OBJECT_ID">=TO_NUMBER(:X) AND "OBJECT_ID"<=TO_NUMBER(:Y))
16 rows selected.
从上述结果可以看出,使用explain plan命令得到的执行计划显示目标sql走的是对索引IDX_T1索引范围扫描。
但是实际情况时怎样的?我们实际执行该sql:
SQL> exec :x:=0;
PL/SQL procedure successfully completed.
SQL> exec :y:=10000;
PL/SQL procedure successfully completed.
SQL> select count(*) from t1 where object_id between :x and :y;
COUNT(*)
----------
19610
SQL> select * from table(dbms_xplan.display_cursor(null,null,‘ADVANCED‘));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
SQL_ID 9dhu3xk2zu531, child number 0
-------------------------------------
select count(*) from t1 where object_id between :x and :y
Plan hash value: 1410530761
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 107 (100)| |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
|* 2 | FILTER | | | | | |
|* 3 | INDEX FAST FULL SCAN| IDX_T1 | 174K| 851K| 107 (1)| 00:00:01 |
---------------------------------------------------------------------------------
52 rows selected.
从上述显示内容可以看到,现在目标sql的执行计划实际上走的是索引IDX_T1的索引快速全扫描,这才是目标sql真实的执行计划,几刚才使用explain plan命令得到的执行计划不是准确的。
同样方法可以得到用set autotrace on方法得到的执行计划也不是准确的。
如何得到真实的执行计划