Oracle优化器

Posted ouyangbuxiu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle优化器相关的知识,希望对你有一定的参考价值。

第一部分:优化器的种类

   a.  CHOOSE (选择性)  b. RULE (基于规则)  c. COST (基于成本)
CHOOSE:
此为缺省值。优化器既可以使用基于成本的优化方法(CBO),也可以使用基于规则的优化方法(RBO),其决定于是否有可用的统计信息。
    如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行过analyze命令有关. 如果table已经被analyze过, 优化器模式将自动成为CBO , 反之,数据库将采用RULE形式的优化器.
    在缺省情况下,ORACLE采用CHOOSE优化器, 为了避免那些不必要的全表扫描(full table scan) , 你必须尽量避免使用CHOOSE优化器,而直接采用基于规则或者基于成本的优化器.
RULE:
不论是否有统计信息存在,优化器都会使用基于规则的方法。
COST:
为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运行analyze 命令,以增加数据库中的对象统计信息(object statistics)的准确性.
CBO根据可用的访问路径和表、索引等对象的统计信息来确定当前SQL的哪个执行计划是最高效的或成本最低的;同时CBO也会考虑Hints的建议。
       CBO执行下列步骤:
a.优化器根据可用的访问路径和Hints为SQL语句产生一组潜在的执行计划。
b.优化器根据数据字典的统计信息评估每个计划的成本。成本就是一个评估值,它与SQL语句按照某个计划执行所消耗的计算机资源是成正比的。优化器基于对计算机资源(I/O、CPU、内存)的评估,计算访问路径和连接顺序的成本。
c.优化器对比执行计划的成本,从而选择一个成本最低的执行计划。
默认情况下,CBO以最佳吞吐量为目标,这意味着Oracle使用尽可能少的资源去处理被语句访问到的所有行;当然CBO也可以用最快的响应速度来优化SQL,这意味着Oracle用尽可能少的资源去处理被语句访问到的第一行或前面少数行,当然这种情况对于整个语句来说可能消耗更多的资源。
       优化器产生的执行计划会因"优化器目标"的不同而不同。如果以最佳吞吐量为目标,结果更倾向于使用全表扫描而不是索引扫描,或者使用排序合并连接而不是嵌套循环连接;如果以最快的响应速度为目标,其结果则通常倾向于使用索引扫描和嵌套循环连接。
       例如,假使你有一个语句既能运行于嵌套循环连接又能运行于排序合并连接,排序合并连接能够较快的返回全部查询结果,而嵌套循环能快速的返回第一行或前面少数行结果。如果你是以提高吞吐量为优化器目标,优化器就会倾向于选择排序合并连接;如果你的优化器目标是提高响应速度,则优化器倾向于选择嵌套循环连接。
       选择优化器目标要以你的应用为基础,一般规则是:
     a.对于批处理应用,以最佳吞吐量为优化目标为好。例如Oracle报表应用程序。
b.对于交互式应用,以最快响应速度为优化目标为好。例如SQLPLUS的查询。
影响优化器优化目标的因素主要有:
a.OPTIMIZER_MODE初始化参数。
b.数据字典中的CBO统计数据。
c.用来改变CBO优化目标的Hints。
 

第二部分:如何设置优化器

设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS .
你当然也在SQL句级或是会话(session)级对其进行覆盖. 你可以在SESSION中改变CBO优化目标:ALTER SESSION SET OPTIMIZER_MODE。例如:
       1、在初始化参数文件中加入如下语句,可以在实例级改变CBO优化目标:
              OPTIMIZER_MODE=FIRST_ROWS_1
       2、下面的语句可以改变当前SESSION的CBO优化目标:
              ALTER SESSION SET OPTIMIZER_MODE=FIRST_ROWS_1

3、可以改变CBO优化目标的Hints
       使用如下Hints可以单独为具体的SQL指定CBO优化目标,SQL语句中Hints能够覆盖OPTIMIZER_MODE初始化参数。
 

第三部分:OPTIMIZER_MODE
 

Value
Description
CHOOSE
此为缺省值。优化器既可以使用基于成本的优化方法(CBO),也可以使用基于规则的优化方法(RBO),其决定于是否有可用的统计信息。
1、  如果在被访问的表中,至少有一个表在数据字典中有可用的统计信息存在,则优化器使用基于成本的方法。
2、  如果在被访问的表中,只有部分表在数据字典中有可用的统计信息,优化器仍然会使用基于成本的方法,但是优化器必须为无统计信息的表利用一些内部信息去尝试其他的统计,比如分配给这些表的数据块的数量等,这可能会导致产生不理想的执行计划。
3、  如果在被访问的表中,没有一个表在数据字典中有统计信息,则优化器使用基于规则的方法。
ALL_ROWS
不论是否有统计信息存在,优化器都使用基于成本的方法,并以最佳吞吐量为优化目标。
FIRST_ROWS_n
不论是否有统计信息存在,优化器都使用基于成本的方法,并以最快的速度返回前n行数据集,n可以是1,10,100,1000。
FIRST_ROWS
优化器使用成本与试探法混合的方式,去寻找一个可以最快返回前面少数行的执行计划。
注:CBO使用试探法产生的执行计划,其成本可能会比不使用试探法要大。FIRST_ROWS可用于向后兼容和计划稳定性。
RULE
不论是否有统计信息存在,优化器都会使用基于规则的方法。

 
 

第四部分:执行计划

       Oracle用来运行一个语句的步骤就叫做执行计划(execution plan),执行计划包含了语句所涉及的每个表的访问路径和连接顺序。
       使用EXPLAIN PLAN语句可以查看优化器所选择的执行计划,首先要创建PLAN_TABLE,用来存放执行计划的描述信息:
              @$ORACLE_HOME/RDBMS/ADMIN/UTLXPLAN.SQL
        Oracle公司建议你在做完数据库版本升级之后删除PLAN_TABLE然后再重建,因为字段可能会有所变化,这可能会导致脚本失效或TKPROF失效。
        这样就可以在pl/sql develop等工具中直接使用EXPLAN PLAY察看SQL语句的执行计划了。
EXPLAIN PLAN 是一个很好的分析SQL语句的工具,它甚至可以在不执行SQL的情况下分析语句. 通过分析,我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称.
        你需要按照从里到外,从上到下的次序解读分析的结果. EXPLAIN PLAN分析的结果是用缩进的格式排列的, 最内部的操作将被最先解读, 如果两个操作处于同一层中,带有最小操作号的将被首先执行.
        NESTED LOOP是少数不按照上述规则处理的操作, 正确的执行路径是检查对NESTED LOOP提供数据的操作,其中操作号最小的将被最先处理.

第五部分:全表扫描

相关概念:
I/O 是针对数据块的而不是行
       Oracle的I/O是针对数据块的,因此被访问的数据块所占的百分比将影响CBO是否选择全表扫描。通常一个数据块中存储着多条记录,被请求的记录要么聚集在少数几个块中,要么分散在大量的数据块中。
HWM(High Water Mark)
       HWM是全表扫描范围的标记,每个全表扫描都要读到HWM位置。当表analyze之后可以在DBA_TABLES.BLOCKS查到HWM,当表被drop、truncate或者move之后,HWM将会被重置。需要注意的是,当一个表被大量删除记录之后,HWM下面的大量数据块是空的,此时若对此表进行全表扫描,Oracle仍然会读到HWM位置,会对全表扫描的性能产生极坏的影响。
全表扫描(Full Table Scans)
    全表扫描将读取HWM之下的所有数据块,访问表中的所有行,每一行都要经WHERE子句判断是否满足检索条件。当Oracle执行全表扫描时会按顺序读取每个块且只读一次,因此如果能够一次读取多个数据块,可以提高扫描效率,初始化参数DB_FILE_MULTIBLOCK_READ_COUNT用来设置在一次I/O中可以读取数据块的最大数量。
优化器何时会使用全表扫描
1、无可用索引
如下面例子:
SELECT last_name, first_name
             FROM employees
       WHERE UPPER(last_name)='TOM'
last_name字段有索引,但在查询中使用了函数,因此该查询不会使用索引。如果想让这个查询走索引,则需要建立函数索引create index ind_upper_lastname on last_name (upper(last_name))。特别要注意的是隐式转换,比如colx字段是varchar2型但存放数字:where colx=123456,这时会发生隐式转换TO_NUMBER(colx),此时colx上的索引也会失效。

2、大量数据
       如果优化器认为查询将会访问表中绝大多数的数据块,此时就算索引是可用的也会使用全表扫描。

3、小表
       如果一个表HWM之下的数据块比DB_FILE_MULTIBLOCK_READ_COUNT要少,只需要一次I/O就能扫完,则使用全表扫描要比使用索引的成本低,此时会使用全表扫描。
       如果有这样小表访问频率又高,通常把它固定在内存中为好alter table table_name storage(buffer_pool keep)。

4、并行
       如果在表一级设置了较高的并行度,如alter table table_name parallel(degree 10),通常会使CBO错误的选择全表扫描。通常不建议在表级的设置并行。
       并行查询通常可以提高全表扫描的性能,建议在语句级用HINTS来实现并行,如/*+full(table_name) parallel(table_name degree)*/。

5、全表扫描hints
       如果想强制优化器使用全表扫描可以用提示FULL。

第六部分:Rowid扫描

       Rowid就是一个记录在数据块中的位置,由于指定了记录在数据库中的精确位置,因此rowid是检索单条记录的最快方式。
       如果通过rowid来访问表,Oracle首先需要获得被检索记录的rowid,Oracle可以在WHERE子句中得到rowid,但更多的是通过索引扫描来获得,然后Oracle基于rowid来定位被检索的每条记录。

优化器何时使用Rowid
       并不是每个索引扫描都伴随着rowid的访问,如果索引中包含了被访问的所有字段,则不再需要通过rowid来访问表。


注意:
       Rowid是Oracle表示数据存储的内部方法,它可能会由于版本的改变而改变。不推荐通过在WHERE中指定rowid来访问数据,因为行迁移和行链接会导致rowid变化,exp/imp也会使rowid变化。
  

第七部分:索引

索引扫描
 
       索引不仅包含被索引字段的值,还包含表中行的位置标识rowid,如果语句只检索索引字段,Oracle直接从索引中读取该值而不去访问表,如果语句通过索引检索其他字段值,则Oracle通过rowid访问表中记录。
 
索引扫描类型:
索引唯一扫描(Index Unique Scans)
索引范围扫描(Index Range Scans)
索引降序范围扫描(Index Range Scans Descending)
索引跳跃扫描(Index Skip Scans)
全索引扫描(Full Scans)
快速全索引扫描(Fast Full Index Scans)
索引连接(Index Joins)
位图连接(Bitmap Joins)

1、索引唯一扫描
       这种扫描通常发生在对一个主键字段或含有唯一约束的字段指定相等条件时,只有单行记录被访问。

2、索引范围扫描
       索引范围扫描是检索数据的常用方式,返回的数据返照索引字段升序排列,字段值相同的则按照rowid升序排列。如果在语句中指定了order by字句,而且排序字段是索引字段时Oracle将忽略order by子句。

 
例如:
SQL> select * from t;
           COLX            COLY
---------------      ---------------
              1               3
              1               2
              1               1
              1               0
SQL> create index ind_t on t(coly);
SQL> set autotrace on
SQL> select * from t where coly>0;
           COLX            COLY
---------------       ---------------
              1               1
              1               2
              1               3
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'IND_T' (NON-UNIQUE)

 
没有使用order by结果集已经是按coly升序排列的。
SQL> set autotrace traceonly
SQL> select * from t where coly>0 order by coly;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T'
   2    1     INDEX (RANGE SCAN) OF 'IND_T' (NON-UNIQUE)
可以看到执行计划中无SORT 步骤,说明Oracle忽略了order by子句。

3、索引降序范围扫描
       如果在order by中指定了索引是降序排列的,或者使用了index_desc提示,Oracle可能会使用索引降序范围扫描。
例如:
SQL> select /*+index_desc(t ind_t)*/colx,coly from t where coly<3;
           COLX            COLY
---------------         ---------------
              1               2
              1               1
              1               0
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=4 Bytes=104)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=4 Bytes=104)
   2    1     INDEX (RANGE SCAN DESCENDING) OF 'IND_T' (NON-UNIQUE) (Cost=2 Card=1)

4、索引跳跃式扫描
       跳跃式扫描发生在复合索引中,它在逻辑上将索引分离为较小的子索引,当复合索引的某一个字段不在查询中指定时,它将被跳过,从而提高索引扫描的效率。可以使用index_ss提示强制使用跳跃扫描。
 
举个例子:
SQL> select* from employees;

 
SEX      EMPLOYEE_ID  ADDRESS
----          ---------------      --------------------
F                 98      ABC
F                100      ABC
F                102      ABC
F                104      ABC
M                101     ABC
M                103     ABC
M                105     ABC
SQL> create index ind_sex_empid on employees(sex,employee_id);
索引结构如下图所示:
SQL>set autotrace traceonly
SQL>select/*+index_ss(employees ind_sex_empid)*/* from employees where employee_id=101;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=11)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (Cost=3 Card=1 Bytes=11)
   2    1     INDEX (SKIP SCAN) OF 'IND_SEX_EMPID' (NON-UNIQUE) (Cost=2 Card=1)

5、全索引扫描
       如果要使用全索引扫描必须满足两个条件,一是查询涉及的字段都包含在索引中,二是至少一个索引字段具有非空属性。由于索引键的数据是有序的,因此全索引扫描可以用消除排序操作。全索引扫描只需要一次I/O。
select empno,ename from big_emp order by empno,ename;
 
Execution Plan
--------------------------------------------------------------------------------
0     SELECT STATEMENT   Optimizer=CHOOSE (Cost=3 Card=1 Bytes=11)
1    0     INDEX (FULL SCAN) OF ‘BE_IX' (Cost=2 Card=1)
6、快速全索引扫描
       快速全索引扫描只访问索引本身,而不去访问表,因此只有查询涉及的字段都包含在索引中时才会使用快速全索引扫描。如果想使用快速全索引扫描查询所涉及的字段必须全部包含在索引中,而且索引中至少有一个字段具有非空属性。满足条件后可以使用index_ffs提示来强制使用快速全索引扫描,快速全索引扫描只适用于CBO。
快速全索引扫描并不能消除排序操作,因为索引键中的数据没有被排序。不同于全索引扫描,快速全索引扫描是通过多块读取的方式来读取整个索引的,并可以设置并行方式。

7、索引连接
       只有查询涉及的所有字段都包含在索引中,才会使用索引连接,此时只通过访问索引就能获得所有需要的数据,而不用访问表。索引连接只适用于CBO,且不能消除排序操作。
       可以通过index_join提示来强制使用索引连接。

8、位图连接
       位图连接使用一个位图作为键,然后通过映射函数将比特位转换为rowid。只有Oracle9i企业版才支持位图索引和位图索引连接。
Sample Table Scans
 
       Sample table scan是随机检索表中的数据,当FROM后面有SAMPLE或SAMPLE BLOCK子句时,会执行Sample table scan。
如:SELECT * FROM employees SAMPLE BLOCK (1);
 
CBO如何选择访问路径
 
       CBO首先检查WHERE子句中的条件以及FROM子句,确定有哪些访问路径是可用的。然后CBO使用这个访问路径产生一组可能的执行计划,再通过索引、表的统计信息评估每个计划的成本,最后优化器选择成本最低的一个。

 
例1:
SELECT *
  FROM employees
  WHERE last_name = 'JACKSON';
       如果last_name具有唯一约束或者主键约束,优化器了解到只有一行数据被返回,这种情况下查询具有很强的选择性,优化很可能走唯一索引扫描。

 
例2:
还是上面的语句,如果last_name不具有唯一约束或主键约束,优化器使用USER_TAB_COLUMNS.NUM_DISTINCT和USER_TABLES.NUM_ROWS的统计信息来评估查询的选择性,估算last_name为jackson的记录占了employees表的比例。

 
例3:
SELECT *
  FROM employees
  WHERE employee_id < 7500;
       评估这个查询的选择性时优化器使用WHERE子句中的边界值7500和employee_id字段的USER_TAB_COLUMNS.HIGH_VALUE、USER_TAB_COLUMNS.LOW_VALUE,优化器假定在最小值和最大值之间employee_id是平均分布的,优化器确定值小于7500的百分比,然后把这个值作为这个查询的选择性。

 
例4:
SELECT *
  FROM employees
  WHERE employee_id < :e1;
       优化器并不知道e1的值,绑定变量的值每次运行都可能不同,因此优化器不能使用前面的方法来评估含有绑定变量的查询的选择性,在这种情况下优化器会使用内部缺省值试探着估算一个选择性。

 
例5:
SELECT *
  FROM employees
  WHERE employee_id BETWEEN :low_e AND :high_e;
优化器会将这句改写为:
employee_id >= :low_e
employee_id <= :high_e
然后优化器仍然是用内部缺省值来试探着为其评估一个选择性。

 
例6:
SELECT *
  FROM employees
  WHERE employee_id BETWEEN 7500 AND 7800;
优化器会改写为:
employee_id >= 7500
employee_id <= 7800
优化器为每个条件独立的评估选择性(S1和S2),然后用下列公式计算BETWEEN的选择性:S=ABS(S1+S2-1)
 

第八部分:连接

理解连接
 
CBO如何运行连接语句
 
为一个连接语句选择一个执行计划,优化器必须做出下列相关决策:
1、 访问路径
优化器必须给连接语句中的每个表选择一个可用来检索数据的路径。

2、连接方法
       Oracle必须为每对行源执行连接操作,连接的方法包括嵌套循环、排序合并、散列连接、笛卡尔积等。

3、连接顺序
       如果需要连接的表多于两个,Oracle先连接其中两个表然后将其连接的结果与下一个表做连接,直到所有的表都被连接。
 
CBO如何选择连接方法
 
       优化器评估每个连接方法的成本,然后选择成本最低的一个。如果一个返回多行的连接,优化器将考虑如下三个因素:
当返回大量的结果集(大于1万行),嵌套循环连接是效率很低的,优化器可能不会选择它。嵌套循环的成本主要在把外表中所有被选择的行与内表匹配的过程,CBO的连接顺序可以用ORDERED提示来改变。嵌套循环连接的成本计算公式:
       cost = access cost of A + (access cost of B * number of rows from A)
如果你使用CBO,当返回大量结果集时使用散列连接效率是非常高的。散列连接的成本计算公式:
       cost = (access cost of A * number of hash partitions of B) + access cost of B
如果你使用RBO,当返回大量结果集时排序合并连接的效率比较高。排序合并连接的成本主要在于把所有行源读到内存中,进行排序的过程,多块读取对排序合并连接会有所帮助。排序合并连接的成本计算公式:
       cost = access cost of A + access cost of B + (sort cost of A + sort cost of B)
       当数据是预先排序的,后面两个排序成本为0。
 
CBO如何运行Anti-joins
 
SELECT * FROM employees
  WHERE department_id NOT IN
    (SELECT department_id FROM departments
      WHERE location_id = 1700);
       优化器缺省是用嵌套循环来处理anti-joins的,但是如果使用了MERGE_AJ、HASH_AJ、NL_AJ提示,NOT IN能够被转换为一个排序合并或hash anti-join。
 
CBO如何运行Semi-joins
 
SELECT * FROM departments
  WHERE EXISTS
    (SELECT * FROM employees
      WHERE departments.department_id = employees.department_id
        AND employees.salary > 2500);
    优化器缺省也是用嵌套循环来执行EXISTS的,也同样可以通过MERGE_SJ、HASH_SJ、NL_SJ提示来调整。通常建议把NOT IN用EXISTS来改写,但是当NOT IN的子查询中包含OR分支时不能转为EXISTS。
 
嵌套循环连接
 
    对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择。在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个查询返回的结果集不能太大(大于1万不适合),要把返回子集较小表的作为外表(CBO默认外表是驱动表),而且在内表的连接字段上一定要有索引。当然也可以用ORDERED提示来改变CBO默认的驱动表,使用USE_NL(table_name1 table_name2)可是强制CBO执行嵌套循环连接。
 
散列连接
 
       散列连接是CBO做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。
       这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。但是在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要有较大的临时段从而尽量提高I/O的性能。
       也可以用USE_HASH(table_name1 table_name2)提示来强制使用散列连接。如果使用散列连接HASH_AREA_SIZE初始化参数必须足够的大,如果是9i,Oracle建议使用SQL工作区自动管理,设置WORKAREA_SIZE_POLICY为AUTO,然后调整PGA_AGGREGATE_TARGET即可。
 
排序合并连接
 
       通常情况下散列连接的效果都比排序合并连接要好,然而如果行源已经被排过序,在执行排序合并连接时不需要再排序了,这时排序合并连接的性能会优于散列连接。可以使用USE_MERGE(table_name1 table_name2)来强制使用排序合并连接。
       以下情况Oracle可能会选择使用排序合并连接:
l         两个表做非等值连接
l         OPTIMIZER_MODE被设置成RULE
l         HASH_JOIN_ENABLE设置成FALSE
l         已经事先排过序,优化器认为使用排序合并连接的成本要比散列连接低。
l         HASH_AREA_SIZE和SORT_AREA_SIZE设置太小,优化器认为散列连接成本过高。
 
外连接
 
       不论是嵌套循环外连接还是散列外连接,CBO不会根据成本去选择连接顺序,被驱动的表总是含有(+)的一方。
SQL> select /*+ordered use_nl(t1 t2)*/ t1.msisdn,t2.msisdn from t1,t2 where t1.msisdn(+)=t2.msisdn;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=99 Bytes=2178)
   1    0   NESTED LOOPS (OUTER) (Cost=100 Card=99 Bytes=2178)
   2    1     INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
   3    1     INDEX (RANGE SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=11)
 
       虽然使用了ORDERED提示,试图以t1作为驱动表,可是由于是外连接,仍然是以t2作为驱动表。
换成散列连接也是一样:
SQL> select /*+ordered use_hash(t1 t2)*/ t1.msisdn,t2.msisdn from t1,t2 where t1.msisdn(+)=t2.msisdn;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=99 Bytes=2178)
   1    0   HASH JOIN (OUTER) (Cost=4 Card=99 Bytes=2178)
   2    1     INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
   3    1     INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
       此时,外表为t1,内表为t2,连接保留t1表与t2不匹配的行,然后用t1构建散列表,最后由t2表去探测t1生成的散列表。
 
Full Outer Joins

select t1.msisdn,t2.msisdn
from t1
full outer join t2
  on t1.msisdn=t2.msisdn
order by t2.msisdn
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=1000 Bytes=36000)
   1    0   SORT (ORDER BY) (Cost=15 Card=1000 Bytes=36000)
   2    1     VIEW (Cost=6 Card=1000 Bytes=36000)
   3    2       UNION-ALL
   4    3         NESTED LOOPS (OUTER) (Cost=2 Card=999 Bytes=21978)
   5    4           INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
   6    4           INDEX (RANGE SCAN) OF 'IND_T2' (NON-UNIQUE)
   7    3         HASH JOIN (ANTI) (Cost=4 Card=1 Bytes=22)
   8    7           INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
   9    7           INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
       从执行计划看,实际上就是做了两个外连接,一个是t1.msisdn=t2.msisdn(+)走嵌套循环,一个是t1.msisdn(+)=t2.msisdn走散列连接,然后再UNION-ALL两个行集。
结果像下面这样:
T1.MSISDN    T2.MSISDN
------------- -----------
           10         10
           20         201
           20         202
           30         114
           30         115
           30         116
...
          270
          280
                      178
                      207
 
 

第九部分:一些和优化器相关的初始化参数


1、OPTIMIZER_FEATURES_ENABLE
       每个版本的Oracle优化器特性都不相同,特别是做了版本升级以后一定要修改这个参数才可以使用仅被该版本支持的优化器特性。
       可以赋予它的值如:9.2.0、9.0.2、9.0.1、8.1.7、8.1.6等。
 
2、CURSOR_SHARING
       这个参数会将SQL语句中的常量用变量来替换,存在大量常量的OLTP系统可以考虑启用这个参数。但是有一点要明白,绑定变量虽然可以使大量的SQL重用,减少分析时间,但是当数据分布发生变化后,CBO为绑定变量SQL生成的执行计划可能不是最优(不会考虑具体的变量值)。通常OLTP系统适用于绑定变量,OLTP系统特点是,SQL运行频繁且时间相对较短,SQL的分析时间比重较大。如果在DSS系统中,SQL运行时间长,相比之下分析时间微不足道,好的执行计划才是最重要的,因此DSS系统绑定变量要另行考虑。
 
3、HASH_AREA_SIZE
       这是散列连接时散列表的存放区域,如果使用散列连接这个参数值不能过小,如果散列表不能完全存放于内存中,对散列连接性能的影响很大。如果是9i建议启动工作区自动管理,然后设置PGA_AGGREGATE_TARGET。
 
4、SORT_AREA_SIZE
       内存排序区的大小,如果排序时内存区不够会写如磁盘。9i同样建议启动工作区自动管理,然后设置PGA_AGGREGATE_TARGET。
 
5、HASH_JOIN_ENABLED
       如果启用这个参数,CBO在考虑连接方法的时候将会考虑散列连接。
 
6、OPTIMIZER_INDEX_CACHING
       这个参数表示被缓存的索引块所占百分比,可选值的范围是0-100。这个值会影响嵌套循环连接,如果这个值设得较高,CBO将更倾向使用嵌套循环。
 
7、OPTIMIZER_INDEX_COST_ADJ
       优化器利用这个参数(是个百分比)把索引扫描的成本转换为等价的全表扫描的成本,然后与全表扫描的成本进行比较。缺省值100,表示索引扫描成本与全表扫描成本等价。可选值范围是0-10000。
 
8、OPTIMIZER_MAX_PERMUTATIONS
       这个初始参数用来设定优化器最多考虑多少种连接顺序,优化器不断的产生可能的表的连接的排列,直到排列数达到参数optimizer_max_permutations为止。一旦优化器停止产生新的可能连接排列,它将会从中选择出成本最小的排列。
 
9、DB_FILE_MULTIBLOCK_READ_COUNT
       这个参数表示在全表扫描或索引快速全扫描时一次I/O读的连续数据块数量(block#连续,且一次I/O不能超过extent)。
 
10、OPTIMIZER_MODE
       优化器模式,也是优化器的优化目标。值为:RULE、CHOOSE、ALL_ROWS、FIRST_ROWS_n、FIRST_ROWS。
 
11、PARTITION_VIEW_ENABLED
       如果设置为TRUE, 该优化器将跳过分区视图中未被请求的分区,该参数还能更改基于成本的优化程序从基础表统计信息计算分区视图统计信息的方式。
   
12、QUERY_REWRITE_ENABLE
       如果设置为TRUE,优化器将利用可用的物化视图来重写SQL。

以上是关于Oracle优化器的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 优化器

Oracle优化器

Oracle优化器

Oracle优化器

怎样查看oracle的优化器参数

Oracle 查看相关优化器参数