
看懂执行计划是作为处理数据库性能问题的必备技能之一,接下来一系列的文章我会告诉你怎么去做。
什么是执行计划
select
t1.v1, t2.v1
From
t1, t2
where
t1.n2= :b1
And t2.id= t1.id
And t2.n2 between :b2 and :b3;
预测的执行计划
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 32 | 832 | 46 (3)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 32 | 832 | 46 (3)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | T1 | 32 | 416 | 24 (5)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T2 | 500 | 6500 | 22 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | T2_N2 | 45 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_NUMBER(:B2)<=TO_NUMBER(:B3))
2 - access("T2"."ID"="T1"."ID")
3 - filter("T1"."N2"=TO_NUMBER(:B1))
5 - access("T2"."N2">=TO_NUMBER(:B2) AND "T2"."N2"<=TO_NUMBER(:B3))
真实的执行计划
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 33 (100)| |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS | | 17 | 442 | 33 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 17 | 442 | 33 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 17 | 221 | 16 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | T1_N2 | 17 | | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | T2_PK | 1 | | 0 (0)| |
|* 7 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 13 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(:B3>=:B2)
5 - access("T1"."N2"=:B1)
6 - access("T2"."ID"="T1"."ID")
7 - filter(("T2"."N2">=:B2 AND "T2"."N2"<=:B3))
这两个执行计划明显不同——即使我是在相同的会话里分别产生的。主要的不同体现在两方面;第一,计划主体行数不同,一个6行,另一个有8行;另一个就是Predicate Information(谓词信息),一个明确显示了所有绑定变量的强制转换(e.g. to_number(:B3)),另一个并没有这种信息。如果我们要根据执行计划来帮助我们提高系统的效率,我们必须知道为什么会有这种自相矛盾现象发生,并且需要确定可以多大程度上相信Oracle给我们的执行计划。接下来一起看看常见的获取执行计划的方法以及每种方法的局限性。
获取执行计划
解释计划
explain plan for (your select statement)
select * from table(dbms_xplan.display);
附注:第一个执行计划就是猜测产生自相矛盾的例子:在第4行和第5行,index range scan预估会有45个rowid会被获取到,但是tableaccess预估返回500行数据,45个rowid不可能对应500行数据。这是索引基于range_based的预测,并使用了最小的选择性0.45%,但是同等情况下表的最小选择性为5%,所以导致了这种情况的发生。
explain plan
set statement_id = ‘{string}’
into {schema}.{table}@{db_link}
for {statement};
statement_id默认为空,目标 table就是plan_table(在新的版本中是全局临时表sys.plan_table$的同义词)。可以使用table和statement_id参数来指定想要的语句的执行计划的输出,函数的声明如下:
dbms_xplan.display({plan_table},{statement_id},{formatoptions},{filter option})
如果无参数调用dbms_xplan或者将前两个参数赋值为NULL,则输出最近解释的语句。就输出格式选项而言,plan table里有许多信息可供选择显示,我们会在后续的章节里介绍。filter选项允许你限制plan_table返回的行—这几乎用不到。
Autotrace
set autotrace on
set autotrace traceonly
set autotrace traceonly explain
set autotrace traceonly statistics
set autotrace off
在SQL*PLUS中开启autotrace后,可以输出执行的任意SQL语句的执行计划和执行统计信息。你可以限制只输出执行计划,只输出执行统计信息,或者全部输出,你还可以不输出语句的执行结果(使用traceonly选项)。下面是一个我使用set autotrace to traceonly statistics的输出例子:
1 row selected.
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
36 consistent gets
0 physical reads
0 redo size
471 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
Dbms_xplan.display_cursor()
dbms_xplan.display_cursor({sql_id},{child_number},{formatoptions})
如果不带参数调用,则会返回最近一次执行的SQL执行计划。有许多原因会导致获取不到执行计划,有可能是游标不可用了,不过最常见的原因是没有设置"set serveroutput off",这个获取失败的执行计划是针对跟在执行的语句后的对dbms_output(BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;)的调用,可以看到如下信息:
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
SQL_ID b3s1x9zqrvzvc, child number 0
BEGIN DBMS_OUTPUT.ENABLE(1000000); END;
NOTE: cannot fetch plan for SQL_ID: b3s1x9zqrvzvc, CHILD_NUMBER: 0
Please verify value of SQL_ID and CHILD_NUMBER;
It could also be that the plan is no longer in cursor cache (check v$sql_plan)
8 rows selected.
我调用dbms_xplan.display_cursor()来获得上述的第二个真实的执行计划。这是Oracle根据我绑定变量的值、涉及对象的统计信息以及会话的环境所真正执行的路径。但是它依然只是返回优化器每一步预估的返回的行数,并不是在执行时真正获取的行数。我们会在接下来的话题中继续讨论。
结论




