首页 > 代码库 > PLSQL_性能优化系列04_Oracle Optimizer优化器
PLSQL_性能优化系列04_Oracle Optimizer优化器
2014-09-25 BaoXinjian
一、摘要
1. Oracle优化器介绍
本文讲述了Oracle优化器的概念、工作原理和使用方法,兼顾了Oracle8i、9i以及最新的10g三个版本。理解本文将有助于您更好的更有效的进行SQL优化工作。
2. RBO优化器
RBO是一种基于规则的优化器,随着CBO优化器的逐步发展和完善,在最新的10g版本中Oracle已经彻底废除了RBO。
正在使用Oracle8i或9i的人们或多或少的都会碰到RBO,因此在详细介绍CBO之前,我们有必要简单回顾一下古老的RBO优化器。
在RBO中Oracle根据可用的访问路径和访问路径的等级来选择执行计划,等级越高的访问路径通常运行SQL越慢,如果一个语句有多个路径可走,Oracle总是选择等级较低的访问路径。
3. CBO优化器结构
CBO是基于成本的优化器,它根据可用的访问路径、对象的统计信息、嵌入的Hints来选择一个成本最低的执行计划。
二、RBO优化器
RBO是一种基于规则的优化器,随着CBO优化器的逐步发展和完善,在最新的10g版本中Oracle已经彻底废除了RBO。
1级:用Rowid定位单行
当WHERE子句中直接嵌入Rowid时,RBO走此路径。Oracle不推荐直接引用Rowid,Rowid可能会由于版本的改变而变化,行迁移、行链接、EXP/IMP也会使Rowid发生变化。
2级:用Cluster Join定位单行
两个表做等值连接,一方的连接字段是Cluster Key,且WHERE中存在可以保证该语句仅返回一行记录的条件时,RBO走此路径。
3级:用带用唯一约束或做主键的Hash Cluster Key定位单行
4级:用唯一约束的字段或做主键的字段来定位单行
5级:Cluster Join
6级:使用Hash Cluster Key
7级:使用索引Cluster Key
8级:使用复合索引
9级:使用单字段索引
10级:用索引进行有界限范围的查找
如,column >[=] expr AND column <[=] expr或column BETWEEN expr AND expr
或column LIKE ‘c%’
11级:用索引字段进行无界限的查找
如,WHERE column >[=] expr 或 WHERE column <[=] expr
12级:排序合并连接
13级:对索引字段使用MAX或MIN函数
14级:ORDER BY索引字段
15级:全表扫描
如果可以使用索引RBO会尽可能的去用索引而不是全表扫描,但是在下列一些情况RBO只能使用全表扫描:
如果column1和column2是同一个表的字段,含有条件column1 < column2或column1 > column2或column1 <= column2或column1 >= column2,RBO会用全表扫描。
如果使用column IS NULL或column IS NOT NULL或column NOT IN或column != expr或column LIKE ‘%ABC’时,不论column有无索引,RBO都使用全表扫描。
如果expr = expr2,expr表达式作用了一个字段上,无论该字段有无索引,RBO都会全表扫描。
如果NOT EXISTS子查询以及在视图中使用ROWNUM,也会造成RBO进行全表扫描。
以上就是RBO的全部可用访问路径。
RBO优化器死板的根据规则来选择执行计划显然不够灵活,在RBO中也无法使用物化视图等Oracle提供的新特性,在Oracle8i时CBO已经基本成熟,因此Oracle强烈建议改用CBO优化器。
三、CBO优化器
CBO是基于成本的优化器,它根据可用的访问路径、对象的统计信息、嵌入的Hints来选择一个成本最低的执行计划。
CBO主要包含以下组件:
- 查询转换器(Query Transformer)
- 评估器(Estimator)
- 计划生成器(Plan Generator)
1. 查询转换器
查询语句的形式会影响所产生的执行计划,查询转换器的作用就是改变查询语句的形式以产生较好的执行计划。
从Oracle 8i开始就有四种转换技术:视图合并(View Merging)、谓词推进(Predicate Pushing)、非嵌套子查询(Subquery Unnesting)和物化视图的查询重写(Query Rewrite with Materialized Views)。
1.1 视图合并
如果SQL语句中含有视图,经分析后会把视图放在独立的“视图查询块”中,每个视图会产生一个视图子计划,当为整个语句产生执行计划时,视图子计划会被直接拿来使用而不会照顾到语句的整体性,这样就很容易导致不良执行计划的生成。视图合并就是为了去掉“视图查询块”,将视图合并到一个整体的查询块中,这样就不会有视图子计划产生,执行计划的优良性得到提升。
1.2 谓词推进
不是所有的视图都能够被合并,对于那些不能被合并的视图Oracle会将相应的谓词推进到视图查询块中,这些谓词通常是可索引的或者是过滤性较强的。
1.3 非嵌套子查询
子查询和视图一样也是被放于独立查询块中的,查询转换器会将绝大多数子查询转换为连接从而合并为同一查询块,少量不能被转换为连接的子查询,会将它们的子计划安照一个高效的方式排列。
1.4 物化视图的查询重写
当query_rewrite_enabled=true时,查询转换器寻找与该查询语句相关联的物化视图,并用物化视图改写该查询语句。
2. 关于“窥视”(Peeking)
在Oracle9i中为查询转换器增加了一个功能,就是当用户使用绑定变量时,查询转换器可以“偷窥”绑定变量的实际值。
我们知道使用绑定变量虽然可以有效的减少“硬分析”,但它带来的负面影响是优化器无法根据实际的数据分布来优化SQL,很有可能本可以走索引的SQL却做了全表扫描。
“窥视”正是为了解决这个问题,但是它并没有彻底的解决,Oracle只允许第一次调用时进行“窥视”,接下来的调用即使绑定变量的值发生了变化,也仍然是使用第一次生成的执行计划,这就造成了一个错误的执行计划会被多次使用,10g中的“窥视”也是如此。
3. 评估器
评估器通过计算三个值来评估计划的总体成本:选择性(Selectivity)、基数(Cardinality)、成本(Cost)。
3.1 选择性:
是一个大于0小于1的数,0表示没有记录被选定,1表示所有记录都被选定。
统计信息和直方图关系到选择性值的准确性。
如:name=’Davis’,
如果不存在统计信息评估器将根据所用的谓词来指定一个缺省的选择性值,此时评估器会始终认为等式谓词的选择性比不等式谓词小;
如果存在统计信息而不存在直方图,此时选择性值为1/count(distinct name);
如果存在统计信息也存在直方图,选择性值则为count(name)where name=’Davis’ / count(name)where name is not null。
3.2 基数:
通常表中的行数称为“基础基数”(Base cardinality);
当用WHERE中的条件过滤后剩下的行数称为“有效基数”(Effective cardinality);
连接操作之后产生的结果集行数称为“连接基数”(Join cardinality);
一个字段DISTINCT之后的行数称为“DISTINCT基数”;
“GROUP基数”(Group cardinality)比较特殊,它与基础基数和DISTINCT基数有关,例如:group by colx则GROUP基数就等于基础基数,但是group by colx,coly的GROUP基数则大于max ( distinct cardinality of colx , distinct cardinality of coly )且小于min ( (distinct cardinality of colx * distinct cardinality of coly) , base cardinality)。
3.3 成本:
就是度量资源消耗的单位。可以理解为执行表扫描、索引扫描、连接、排序等操作所消耗I/O、CPU、内存的数量。
4. 计划生成器
计划生成器的作用就是生成大量的执行计划,然后选择其中总体成本最低的一个。
由于不同的访问路径、连接方式和连接顺序可以任意组合,虽然以不同的方式访问和处理数据,但是可以产生同样的结果,因此一个SQL可能存在大量不同的执行计划。
但实际上计划生成器很少会试验所有的可能存在的执行计划,如果它发现当前执行计划的成本已经很低了,它将停止试验,相反当前计划的成本如果很高,它将继续试验其他执行计划,因此如果能使计划生成器一开始就找到成本很低的执行计划,则会大量减少所消耗的时间,这也正是我们为什么用HINTS来优化SQL的原因之一。
四、访问路径
访问路径就是从数据库里检索数据的方式。
优化器首先检查WHERE子句和FROM子句的条件,确定有哪些访问路径是可用的。
然后优化器使用这些访问路径或各访问路径的联合,产生一组可能存在的执行计划,再通过索引、字段、表的统计信息评估每个计划的成本,最后优化器选择成本最低的执行计划所对应的访问路径。
如果SQL语句的FROM子句无SAMPLE或SAMPLE BLOCK,优化器在选择访问路径的时候会优先考虑语句中的HINTS。
优化器可用的访问路径如下:
- 全表扫描(Full Table Scans)
- Rowid扫描(Rowid Scans)
- 索引扫描(Index Scans)
- 簇扫描(Cluster Scans)
- 散列扫描(Hash Scans)
- 表取样扫描(Sample Table Scans)
1. 全表扫描
全表扫描将读取HWM之下的所有数据块,所有行都要经WHERE子句过滤看是否满足条件。当Oracle执行全表扫描时会按顺序读取每个块且只读一次,如果能够一次读取多个块,可以有效的提高效率,初始化参数DB_FILE_MULTIBLOCK_READ_COUNT用来设置在一次I/O中可以读取多少个数据块。 通常我们认为应该避免全表扫描,但是在检索大量数据时全表扫描优于索引扫描,这正是因为全表扫描可以在一次I/O中读却多个块,从而减少了I/O的次数。在使用全表扫描的同时也可以使用并行来提高扫描的速度。
CBO优化器何时会选择全表扫描:
- 1) 无合适的索引。
- 2) 检索表中绝大多数的数据。
- 3) 表非常小。比如,表中的块小于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) 太旧的统计数据。如果表没有进行过分析或很久没有再次分析,CBO可能会错误的认为表含有及少的数据块。
- 6) 在语句中嵌入了全表扫描的HINTS。
2. Rowid扫描
Rowid表示行在数据块中的具体位置,Rowid是查找具体行的最快方式。可以在WHERE子句中写入Rowid,但是不推荐这么做。通常都是通过索引来获得Rowid,但如果被检索的行都包含在索引中时,直接访问索引就能得到所需的数据则不会使用Rowid。
3. 索引扫描
索引不仅包含被索引的字段值,还包含行的位置标识Rowid,如果语句只检索索引字段,Oracle将直接从索引中读取而不需要通过Rowid去访问表,如果语句通过索引检索其他字段值,则Oracle通过索引获得Rowid从而迅速找到具体的行。
索引扫描类型:
- 1) 唯一索引扫描(Index Unique Scans)
- 2) 索引范围扫描(Index Range Scans)
- 3) 索引降序范围扫描(Index Range Scans Descending)
- 4) 跳跃式索引扫描(Index Skip Scans)
- 5) 全索引扫描(Full Index Scans)
- 6) 快速全索引扫描(Fast Full Index Scans)
- 7) 索引连接(Index Joins)
3.1 唯一索引扫描
在利用一个主键字段或含有唯一约束的字段选择一行记录时,通常发生唯一索引扫描。
3.2 索引范围扫描
索引范围扫描返回的数据返照索引字段值升序排列,值相同的按Rowid升序排列。如果在语句中使用了ORDER BY ASC子句,而且排序字段是索引字段时Oracle不会对ORDER BY再次排序。
3.3 索引降序范围扫描
如果在order by中指定了索引是降序排列的,或者使用了INDEX_DESC提示,优化器会使用索引降序范围扫描。
3.4 跳跃式索引扫描
跳跃式索引扫描是用来提高复合索引效率的,通常当复合索引的第一个索引字段不在语句中指定时是无法使用复合索引的,此时如果复合索引的第一个索引字段DISTINCT值非常小,而复合索引的其他索引字段DISTINCT值非常大时,可以使用跳跃式索引扫描来跳过该复合索引的第一个索引字段。跳跃式扫描会使复合索引在逻辑上分裂成N个较小的索引,N值等于复合索引的第一个索引字段的DISTINCT值。
3.5 全索引扫描
当查询涉及的字段都包含在索引中,如果WHERE子句中谓词非第一个索引字段,或无WHERE子句但是被索引字段中至少有一个非空属性时,通常会做全索引扫描。全索引扫描结果集按索引字段排序。
3.6 快速全索引扫描
当查询涉及的字段都包含在索引中,且被索引字段中至少有一个非空属性时,可以使用INDEX_FFS(table_name index_name)来使语句做快速全索引扫描。快速全索引扫描不同于全索引扫描,它使用多块读取的方式来读全部索引块,而且可以使用并行读取。快速全索引扫描的结果集不会排序。位图索引不能使用快速全索引扫描。
3.7 索引连接
索引连接是几个索引的散列连接。如果查询的字段上都存在索引,可以使用索引连接来避免访问表。
4. 簇扫描
在被索引的簇中,有着相同簇键值的行存储在同一数据块中。执行簇扫描时,首先通过扫描簇索引获得被检索行的Rowid,然后使用Rowid来定位具体的行。
5. 散列扫描
散列扫描就是在一个散列簇中定位数据行。在一个散列簇中,具有相同散列值的行存储在相同的数据块中。在执行散列扫描时,首先通过一个散列函数来获得散列值,然后用散列值在数据块中定位具体行。
6. 表取样扫描
当FROM子句后带有SAMPLE或SAMPLE BLOCK时,会执行表取样扫描来随机检索表中的数据。如:select* from t sample block (1);
7. 处理连接
影响一个连接语句执行计划的四个重要因素是:访问路径、连接方式、连接顺序和成本评估。
五、影响优化器的初始化参数
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重用,减少分析时间,但是执行计划可能会不理想。通常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。
********************作者:鲍新建********************
参考: http://blog.itpub.net/18474/viewspace-1060730/
参考: http://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328365.html
PLSQL_性能优化系列04_Oracle Optimizer优化器