我们都希望对于所有在Oracle数据库中执行的SQL,CBO都能产生出正确的执行计划,但实际情况却并非如此,由于各种各样的原因(比如目标SQL所涉及的对象的统计信息的不准确,或者CBO内部一些成本计算公式的先天缺陷等),导致有时CBO产生效率不高、甚至是错误的执行计划。特别是CBO对目标SQL所产生的初始执行计划是正确的,后来由于某种原因(比如统计信息的变更等)而导致CBO重新对其产生了错误的执行计划,这种执行计划的改变往往会导致目标SQL执行时间呈数量级的递增,而且常常会让我们很困惑:这个SQL原先跑得好好的,为什么突然就慢得让人无法接受?其实这种SQL执行效率突然衰减往往是因为目标SQL执行计划的改变。
我们当然希望这样的改变永远不要发生,即在Oracle数据库中跑的所有SQL都能有正确的、稳定的执行计划,但实际上在Oracle 11g的SPM(SQL Plan Management)出现之前,这一点是很难做到的。那么现在退而求其次,如果已经出现了执行坟墓的变更,即CBO已经产生了错误的执行计划,我们应该怎么纠正呢?
我种情况下,我们通常会重新收集一下统计信息或者修改目标SQL(比如在目标SQL中加入Hint等)以纠正错误的执行计划。但有时候重新收集统计信息并不能解决问题,更糟糕的是,很多情况下是没有办法修改目标SQL的SQL文本的(比如第三方开发的系统,修改不了源码,或者目标SQL是前台框架动态生成的等等),那么这种情况下我们该怎么办呢?
在Oracle 10g/11g及其以后的版本中,我们可以使用SQL Profile或SPM(SQL Plan Management)来解决上述执行计划变更的问题,用它们来调整、稳定目标SQL的执行计划。
本文介绍使用SQL Profile来稳定执行计划:
Oracle 10g中的SQL Profile(直译为“SQL概要”)可以说是Oracle 9i中的Stored Outline(直译为“存储概要”)的进化。Stored Outline能够实现的功能SQL Profile也完全能够实现。
与Stored Outline相比,SQL Profile具备如下优点:
更容易生成、更改和控制
在SQL语句的支持上做得更好,也就是说适用范围更广。
使用SQL Profile可以很容易实现如下两个目的:
锁定或者说稳定执行计划
在不能修改目标SQL的SQL文本的情况下使目标SQL语句按指定的执行计划运行。
SQL Profile有两种类型:一种是Automatic类型,另一种是Manual类型。下面分别介绍这两种类型:
1. Automatic类型的SQL Profile
Automatic类型的SQL Profile其实就是针对目标SQL的一些额外的调整信息,这些信息存储在数据字典里。当有了Automatic类型的SQL Profile后,Oracle在产生执行计划时就会根据它对目标SQL所涉及的统计信息等内容做相应的调整,因而能够在一定程度上避免产生错误的执行计划。你不用担心Automatic类型的SQL Profile的准确性,因为Oracle会使用类型于动态采用技术那样的手段来保证这些额外调整信息相对准确。
Automatic类型的SQL Profile不会像Stored Outline那样锁定目标SQL的执行计划,因为Automatic类型的SQL Profile的本质就是针对目标SQL的一些额外的调整信息,这些额外的调整信息需要与原目标SQL的相关统计信息等内容一起作用才能得到新的执行计划,即原始SQL的统计信息等内容一旦发生变化,即使原有Automatic类型的SQL Profile并没有改变,该SQL的执行也可能会发生变化。从这个意义上讲,Automatic类型的SQL Profile并不能完全起到稳定目标SQL的执行计划的作用,虽然它确实可以用来调整执行计划。
看一个在不更改目标SQL的SQL文本的情况下使用Automatic类型的SQL Profile来调整执行计划的实例:
创建测试表及相关操作:
zx@MYDB>create table t1 (n number);
Table created.
zx@MYDB>declare
2 begin
3 for i in 1..10000 loop
4 insert into t1 values(i);
5 end loop;
6 commit;
7 end;
8 /
PL/SQL procedure successfully completed.
zx@MYDB>select count(*) from t1;
COUNT(*)
----------
10000
zx@MYDB>create index idx_t1 on t1(n);
Index created.
zx@MYDB>exec dbms_stats.gather_table_stats(ownname=>USER,tabname=>'T1',method_opt=>'for all columns size 1',cascade=>true);
PL/SQL procedure successfully completed.
zx@MYDB>select /*+no_index(t1 idx_t1) */ * from t1 where n=1;
N
----------
1