您的位置:首页 > 数据库 > Oracle

Oracle优化方式和模式

2017-06-26 00:00 190 查看
Oracle优化器的优化方式和优化模式
Oracle在执行一个SQL之前,首先要分析一下语句的执行计划,然后再按
执行计划去执行。分析语句的执行计划的工作是由优化器(Optimizer)
来完成的。不同的情况,一条SQL可能有多种执行计划,但在某一时点,
一定只有一种执行计划是最优的,花费时间是最少的

1、优化器的优化方式
Oracle的优化器共有两种的优化方式,即
* 基于规则的优化方式(Rule-Based Optimization,简称为RBO)
* 基于代价的优化方式(Cost-Based Optimization,简称为CBO)。
A、RBO方式:优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。

B、CBO方式:依词义可知,它是看语句的代价(Cost)了,这里的代价主要指 Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有多少行、每行的长度等信息。在Oracle8及以后的版本,Oracle推荐用CBO的方式。

我们要明了,不一定走索引就是优的 ,比如一个表只有两行数据,一次IO就可以完成全表的检索,而此时走索引时则需要两次IO,这时对这个表做全表扫描(full table scan)是最好的。

如何设置基于规则和成本的优化方式
Oracle 在执行SQL语句时,有两种优化方法:即基于规则的RBO和基于代 的CBO。在SQL执行的时候,到底采用何种优化方法,就由Oracle参数 optimizer_mode 来决定。SQL> show parameter optimizer_mode NAME TYPE VALUE------------------------------------ ----------- ------------------------------optimizer_mode string CHOOSE optimizer_mode 参数值共有以下四个:

第一:CHOOSE 这个是Oracle的默认值。采用这个值时,Oracle即可以采用基于规则RBO,也可以采用基于代价的CBO,到底使用那个值,取决于当前SQL的被访问的表中是不是有可以使用的统计信息。 如果有统计信息,使用基于代价的优化方法CBO。 如果没有统计信息,Oracle就会采用基于规则的优化方法RBO。
alter system set optimizer_mode=CHOOSE

第二:ALL_ROWS 不管是不是有统计信息,全部采用基于成本的优化方法CBO。
alter system set optimizer_mode=ALL_ROWS

第三:FIRST_ROWS_n 不管是不是有统计信息,全部采用基于成本的优化方法CBO,并以最快的速度,返回前N行记录。

第四:FIRST_ROWS 使用成本和试探法相结合的方法,查找一种可以最快返回前面少数行的方法;这个参数主要用于向后兼容。

第五:RULE 这个参数正好和ALL_ROWS相反,不管是不是统计信息,全部采用基于规则的优化方法。
alter system set optimizer_mode=RULE;

如何更改 optimizer_mode 的参数呢?可以用以下的方法。SQL> alter session set optimizer_mode='RULE';

********************************************************************************************

EXISTS语法
EXISTS 指定一个子查询,检测行的存在。
语法: EXISTS subquery
参数: Subquery
结果类型 Boolean

结果值:
如果子查询包含行,则返回 TRUE。

select a.字段1,a.字段2 from 表1
a where not exists ( select * from 表2 b where b.字段2=a.字段2
)

使用EXISTS去掉重复值
使用连接查询去掉重复值
select distinct d.deptno,d.dname from emp e,dept d where e.deptno=d.deptno
使用EXISTS 去掉重复值
select d.deptno,d.dname from dept d where exists(select * from emp e where e.deptno=d.deptno)

--用exists去掉重复行
select d.deptno,d.dname from dept d where exists(select * from emp e where e.deptno=d.deptno)
* 子查询 exists(select * from emp e where e.deptno=d.deptno)
* 判读为true和false的依据,是该查询有没有返回的行数
select * from emp e where e.deptno=d.deptno 有true 没有false
* ==true 主查询输出
* ==false 主查询不输出
* 先执行主查询,在用主查询中的条件去子查询中执行
select * from emp e where e.deptno=40(变化的)

通过使用EXISTS,Oracle会首先检查主查询,然后运行子查询直到
它找到第一个匹配项,这就节省了时间。Oracle在执行IN子查询时,
首先执行子查询,并将获得的结果列表存放在一个加了索引的临时表
中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,
存放在临时表中以后再执行主查询。这也就是使用EXISTS比使用IN
通常查询速度快的原因。

附录:UNION用法
为了合并多个select语句的查询结果,可以使用集合操作符UNION,UNION ALL
语法如下:
SELECT语句1 [ UNION | UNION ALL ] SELECT语句2
使用集合操作符有以下一些限制:
. * 对于LOB,嵌套表类来说,集合操作符无效
. * 对于LONG型,UNION ALL无效
* .如果选择列表包含了表达式,必须指定别名
1、UNION,用于获取两个结果集的并集,会自动去掉结果集中的重复行,并会以第一列的结果进行排序,例:
select * from emp union select * from emp
2、UNION ALL,与UNION相似,不同的是UNION ALL不会自动去处重复行,也不会以任何列排序
select * from emp union all select * from emp

请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相
似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同

ORACLE-SQL优化 

访问Table的方式
  ORACLE 采用两种访问表中记录的方式:
  a. 全表扫描
    全表扫描就是顺序地访问表中每条记录. ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描.
  b. 通过ROWID访问表
    你可以采用基于ROWID的访问方式情况,提高访问表的效率, , ROWID包含了表中记录的物理位置信息..ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系. 通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高.

清空共享池的语法:

alter system flush buffer_cache

1. 选择最有效率的表名顺序(只在基于规则的优化器中有效)

设置基于规则的优化器

alter system set optimizer_mode=RULE

  ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理. 在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.当ORACLE处理多个表时, 会运用排序及合并的方式连接它们.首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并

例如:
  表 TAB1 16,384 条记录
  表 TAB2 1 条记录
  选择TAB2作为基础表 (最好的方法)
select count(*) from ids_emp,ids_dept  

选择TAB1作为基础表 (不佳的方法)
select count(*) from ids_dept,ids_emp

2 WHERE子句中的连接顺序.
  ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.

效率高

select count(*) from ids_emp e,ids_dept d
where e.deptno=d.deptno and e.sal>5000 and job='总经理' and rownum<10

效率低
select count(*) from ids_emp e,ids_dept d
where rownum<10 and e.deptno=d.deptno and e.sal>5000 and
job='总经理'

3 SELECT子句中避免使用 ‘ * ‘
  当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ‘*' 是一个方便的方法.不幸的是,这是一个非常低效的方法.
实际上,ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间.

4. 计算记录条数
   和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的. 例如 COUNT(EMPNO)
  (在论坛中曾经对此有过相当热烈的讨论, 这个观点并不十分准确,通过实际的测试,上述三种方法并没有显著的性能差别)

设置基于规则的优化器

alter system set optimizer_mode=RULE

select count(empno) from ids_emp --7.25 ---效率高
select count(*) from ids_emp --17.469
select count(ename) from ids_emp --17.219

5. 用Where子句替换HAVING子句
   避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销.
例如

低效:
  select deptno,avg(sal) from ids_emp group by deptno having deptno>20
高效
select deptno,avg(sal) from ids_emp where deptno>20 group by deptno     

(HAVING 中的条件一般用于对一些集合函数的比较,如COUNT() 等等. 除此而外,一般的条件应该写在WHERE子句中)

6. 使用表的别名(Alias)
  当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.
  (Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的 归属)

7. 用EXISTS替代IN
  
  在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率.

设置基于基于成本的优化器

下面连接语句设置基于成本的优化器执行快

alter system set optimizer_mode=ALL_ROWS 效率差不多,采用exists

设置基于规则的优化器 运行结果如下:

alter system set optimizer_mode=RULE

效率低(增加索引) 72秒

select * from ids_emp
where deptno in(select deptno from ids_dept where loc='a'

效率高(增加索引) 18秒

select * from ids_emp e

where exists(select * from ids_dept d where e.deptno=d.deptno

and loc='a')

8. 用NOT EXISTS替代NOT IN
  在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.

效率低(增加索引)

select * from ids_emp

where deptno not in(select deptno from ids_dept where loc='a')

效率高(增加索引)

select * from ids_emp e

where not exists(select * from ids_dept d where e.deptno=d.deptno

and loc='a')

9. 用表连接替换EXISTS
  通常来说 , 采用表连接的方式比EXISTS更有效率
select * from ids_empno e
where exists(select * from ids_deptno d where e.deptno=d.deptno


and loc='a')
更高效
select ename from ids_emp e,ids_dept d where e.deptno=d.deptno and loc='a'

10.. 用EXISTS替换DISTINCT
  当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用 DISTINCT. 一般可以考虑用EXIST替换
  

select distinct d.deptno,d.dname from ids_emp e,ids_deptno d

where e.deptno=d.deptno
高效:
select d.deptno,d.dname from ids_deptno d


where exists( select * from ids_emp e where e.deptno=d.deptno)
11
常量的计算是在语句被优化时一次性完成,而不是在每次执行时。

下面是检索月薪大于2000的表达式:

sal > 24000/12
sal > 2000
sal*12 > 24000

如果SQL语句包括第一种情况,优化器会简单地把它转变成第二种。
优化器不会简化跨越比较符的表达式,例如第三条语句,鉴于此,应尽量写用常量跟字段比较检索的表达式,而不要将字段置于表达式当中。否则没有办法优化,比如如果sal上有索引,第一和第二就可以使用(执行索引),第三就难以使用(sal*12 不识别索引)。

12 IN、OR子句常会使用工作表,使索引失效:
如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。

13 消除对大型表行数据的顺序存取:
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询 10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄??)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的WHERE子句强迫优化器使用顺序存取。

使用设置基于基于成本的优化器来测试

alter system set optimizer_mode=ALL_ROWS scope=both

下面的查询将强迫对ide_emp表执行顺序操作:
select * from ids_emp where(deptno=10 and deptno=20) or empno=3000

时间 17秒左右

虽然在empno上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT * FROM ids_emp WHERE deptno=10 and deptno=20
UNION
SELECT * FROM ids_emp WHERE empno=3000


这样就能利用索引路径处理查询。

时间 0.078秒左右

注:UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条SELECT 语句中的列的顺序必须相同。

14 避免相关子查询:
一个列的标签同时在主查询和WHERE子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Oracle