课程目标

完成本课程的学习后,您应该能够:

•优化器的作用
•优化器的类型
•优化器的优化步骤
•扫描的基本类型
•表连接的执行计划
•其他运算方式的执行计划
•如何看执行计划顺序
•如何获取执行计划
 
1.优化器概述
  oracle中优化器(optimizer)是SQL分析和执行的优化工具,它负责制订SQL的执行计划,也就是负责保证SQL执行的效率最高。
优化器的类型:
基于规则的优化器(RBO,Rule-Based Optimizer)
基于成本的优化器(CBO,Cost-Based Optimizer)

1.1 RBO

  基于规则的优化器诞生于早期关系型数据库,它的原理是基于一系列规则的优先顺序来分析出执行计划,以判断最优查询路径。

     Oracle 课程五之优化器和执行计划

  其中,排名越靠前,Oracle认为效率越高。例如:按索引访问的效率肯定高于全表扫描,多字段复合索引的效率高于单字段索引,等等。通俗地讲,RBO就是不关心被访问对象的实际数据分布情况、索引效率等,仅凭想象去决定应该如何去访问数据库。可见,RBO是一种非常粗放型的优化器。

  RBO的优缺点:

  缺点:
    通过固定的规则来判断执行计划,容易制定出恶性执行计划。
    不通过统计信息来判断,使得误差较大。
  优点:
    RBO的判断有规可循、有律可依,方便用户对优化器的选择进行正确的预测,可以按照我们所期望的方法引导优化器制定执行计划。 

1.2 CBO

  基于成本的优化器是关系型数据库所追求的理想型优化器,它的原理是计算了所有查询方法所需要的成本之后,从中选择一个成本最小的查询路径。

  分析----CBO的数据来源

  a.CBO是一个数学模型
  b.需要准确的传入数据
  c,通过精确的数据计算出精确的执行计划
 

CBO在判断最优路径时,需要通过分析相关的统计信息,这些信息包括:
表中的行数
数据块数
每个数据块中的平均行数
行的平均长度
每个列常数的种类
离散程度(直方图)
列值中null的个数
聚簇因子
索引的高度
最大最小值
叶块的数量
运行系统的IO和CPU的使用情况

    select * from user_tables; select * from user_indexes;

  CBO的优缺点:

  缺点:

    a.无法提前预测执行计划。
    b.控制执行计划比较困难。
    c.少数情况存在执行计划选择错误。

  优点:

    a.即使没有理解优化器的工作原理,大多数情况下也能得到最优化的性能。
    b.通过统计信息控制优化。
  
  优化器的优化步骤:

           a.通过统计信息对所要执行的sql进行解析,在可能存在的执行计划中进行选择,之后制定出临时的执行计划。
           b.优化器通过对直方图、表的存储结构特征、索引的结构、分区类型、比较运算符等信息进行分析之后计算出各个执行计划的成本。
           c.优化器对各个执行计划的成本进行比较,并从中选择一个成本最低的执行计划。

  优化器的组成

    a.查询转换器
    b.成本估算器
    c.执行计划生成器

查询转换包括:
    视图合并
    子查询解嵌套
    谓词前推
    使用物化视图进行查询重写

drop table test1 purge;
drop table test2 purge;
create table test1 as select * from dba_objects where rownum <=100;
create table test2 as select * from dba_objects where rownum <=1000;
exec dbms_stats.gather_table_stats(user,'test1');
exec dbms_stats.gather_table_stats(user,'test2');

select count(1) from test1 t1,test2 t2 where t1.object_id=t2.object_id;
select count(1) from test1 t1 where t1.object_id in
(select t2.object_id from test2 t2);
select count(1) from test1 t1
 where exists (select 1 from test2 t2 where t1.object_id = t2.object_id);

--查看UNPARSED QUERY IS出即是查询转换
Alter system flush shared_pool;
alter session set tracefile_identifier = '1111';
alter session set events '10053 trace name context forever, level  1';
select count(1) from test1 t1,test2 t2 where t1.object_id=t2.object_id;
alter session set events '10053 trace name context off' ;

Alter system flush shared_pool;
alter session set tracefile_identifier = 'in';
alter session set events '10053 trace name context forever, level  1';
select count(1) from test1 t1 where t1.object_id in
(select t2.object_id from test2 t2);
alter session set events '10053 trace name context off' ;

Alter system flush shared_pool;
alter session set tracefile_identifier = 'exists';
alter session set events '10053 trace name context forever, level  1';
select count(1) from test1 t1
 where exists (select 1 from test2 t2 where t1.object_id = t2.object_id);
alter session set events '10053 trace name context off' ;

Set autotrace traceonly
select count(1) from test1 t1,test2 t2 where t1.object_id=t2.object_id;
select count(1) from test1 t1 where t1.object_id in
(select t2.object_id from test2 t2);
select count(1) from test1 t1
 where exists (select 1 from test2 t2 where t1.object_id = t2.object_id);
Set autotrace off
实验:优化器RBO与CBO

1.2.1CBO为什么不走索引

  为什么有时明显会走索引的情况却不走索引?

  •统计信息陈旧、直方图信息有误。
  •选择其他扫描的代价更低。
Drop table tt purge;
create table tt as select * from dba_objects;
create index ind_status on tt(status);
exec dbms_stats.gather_table_stats(user,'TT',cascade=>true);
set autotrace traceonly
set timing on
set linesize 1000
update tt set status='INVALID' where object_id in (10);
Commit;
select * from tt where status='INVALID';
exec dbms_stats.gather_table_stats(user,'TT',cascade=>true);
select * from tt where status='INVALID';
实验:CBO不走索引

  RULE:基于规则的方式。
  CHOOSE (默认) :如果有统计信息,走CBO;如果没有统计信息且动态采集级别设置为0,走RBO。


  CBO优化器有两种可选的运行模式:
  FIRST_ROWS:以最低的成本返回查询的最先几行。
  ALL_ROWS:以最低的成本返回所有行。

drop table test purge;
create table test as select * from dba_objects;
insert /*+append*/ into test select * from test;
Commit;
insert /*+append*/ into test select * from test;
Commit;
insert /*+append*/ into test select * from test;
Commit;
insert /*+append*/ into test select * from test;
Commit;
insert /*+append*/ into test select * from test;
commit;
select count(*) from test;
create index idx_name on test(owner,object_name) nologging;
exec dbms_stats.gather_table_stats(user,'TEST',cascade=>true);

Set timing on
Set autotrace traceonly
Set linesize 1000
SELECT /*+all_rows*/*
  FROM (SELECT /*+all_rows*/
         INNER_TABLE.*, ROWNUM OUTER_TABLE_ROWNUM
          FROM (select /*+all_rows*/
                 owner, object_name, created
                  from test
                 where owner = 'SYS'
                 order by object_name) INNER_TABLE
         WHERE rownum <= 18)
 WHERE OUTER_TABLE_ROWNUM > 1;
      
SELECT /*+first_rows(18)*/*
   FROM (SELECT /*+first_rows(18)*/
          INNER_TABLE.*, ROWNUM OUTER_TABLE_ROWNUM
           FROM (select /*+first_rows(18)*/
                  owner, object_name, created
                   from test
                  where owner = 'SYS'
                  order by object_name) INNER_TABLE
          WHERE rownum <= 18)
  WHERE OUTER_TABLE_ROWNUM > 1;
优化器模式

 2.执行计划

  执行计划的重要性:当分析一条SQL语句的性能时,通常最先做的事情就是分析它的执行计划,如果连执行计划都看不懂,那SQL调优根本无从谈起。
  执行计划:从表中读出数据并且生成查询语句所要求结果的查询路径。简单点说是SQL语句访问和处理数据的方式。

  执行计划的类型:执行计划的最基本类型实际上就是查询且读取物理数据的方式,该方式被称为扫描。当需要从一个以上的表中读取数据时,必然需要将这些表进行连接,这样的执行类型被称为表连接。

  a.扫描的执行计划
  b.表连接的执行计划
  c.其他运算方式的执行计划
  
2.1扫描的执行计划-全表扫描、rowid扫描

  全表扫描(full   table   scan)扫描对象表中高水位线以下的所有数据块,包括空数据块,同时通过对where 条件中查询条件的过滤来筛选出满足所有条件的数据行的过程。

  谓词access和filter的区别
drop table test purge;
create table test as select * from dba_objects;
exec dbms_stats.gather_table_stats(user,'test');
set autotrace  traceonly
select * from test where object_id=100;
create index ind_object_id on test(object_id) nologging;
select * from test where object_id=100;
谓词access和filter的区别
  可以看到Access Predicate和Filter Predicate的重要区别:
Access Predicate在访问数据时做判断,不满足条件的数据不会形成Row Source;
Filter Predicate对已产生的Row Source再做判断,不满足条件的则被丢弃(Throw-Away)。
而降低执行计划中的Throw-Away是我们做SQL调优的一项重要参考指标,因此,一些将Filter Predicate转为Access Predicate的方法也是我们的重要调优手段。
 
2.2扫描的执行计划-索引扫描

  索引扫描类型:

a.索引唯一扫描(index unique scan)
b.索引范围扫描(index range scan)
c.索引降序范围扫描(index range scan descending)
d.索引跳跃式扫描(index skip scan)
e.索引全扫描(index full scan)
f.索引快速全扫描(index fast full scan)
g.位图索引(bitmap index)
drop table test purge;
create table test as select * from dba_objects where object_id is not null;
alter table test modify object_id not null;
create unique index ind_object_id on test(object_id) nologging;
create index ind_object_name on test(object_name) nologging;
exec dbms_stats.gather_table_stats(user,'test',cascade => true);
set autotrace  trace exp
--索引唯一扫描:
select * from test where object_id = 100;
--索引范围扫描
select * from test where object_name = 'C_COBJ#';
select * from test where object_id < 100;
--索引降序范围扫描
select * from test where object_id >100 and object_id <110 order by object_id desc;
--索引全扫描
select * from test order by object_id;
--索引快速全扫描
select object_id from test;
--索引跳跃式扫描
drop index ind_object_id;
create index ind_type_id on test(object_type,object_id);
select * from test where object_id=100;
--位图索引
create bitmap index bit_status on test(status);
select count(*) from test where status='VALID';
实验:索引扫描类型
 
2.3表连接的执行计划类型

  表连接类型:

a.嵌套循环连接(nested loops join)
b.排序合并连接(sort merge join)
c.哈希连接(hash join)
d.半连接(semi join)
e.外连接(outer join)
f.索引连接(index join)
g笛卡尔连接(cartestian join)
 
2.3.1表连接的执行计划-嵌套循环连接
 
嵌套循环连接(nested loops join):
访问次数:驱动表返回几条,被驱动表访问多少次。
驱动表是否有顺序:有。
是否要排序:否。
应用场景: 1. 关联中有一个表比较小;
            2. 被关联表的关联字段上有索引;
            3. 索引的键值不应该重复率很高。
通过实验一起来探究nested loops join的原理吧!
set linesize 1000
Set pagesize 100
drop table test1 purge;
drop table test2 purge;
create table test1 as select * from dba_objects where rownum <=100;
create table test2 as select * from dba_objects where rownum <=1000;
exec dbms_stats.gather_table_stats(user,'test1');
exec dbms_stats.gather_table_stats(user,'test2');
1.创建嵌套环连接例子
  basic的情况下,oracle关闭了所有性能数据的收集, 如果要关闭AWR收集
默认为typical的时,除了plan_executetion_statistics和OS Statistics不能收集外,其他的都可以收集
all,所有的都要收集。
alter session set statistics_level=all;
select count(*) from test1 t1, test2 t2
 where t1.object_id = t2.object_id;
设置SQL统计信息等级

Starts为该sql执行的次数。
E-Rows为执行计划预计的行数。
A-Rows为实际返回的行数。A-Rows跟E-Rows做比较,就可以确定哪一步执行计划出了问题。
A-Time为每一步实际执行的时间(HH:MM:SS.FF),根据这一行可以知道该sql耗时在了哪个地方。
Buffers为每一步实际执行的逻辑读或一致性读。
Reads为物理读。
OMem、1Mem为执行所需的内存评估值,0Mem为最优执行模式所需内存的评估值,1Mem为one-pass模式所需内存的评估值。
0/1/M 为最优/one-pass/multipass执行的次数。
Used-Mem耗的内存

 

select /*+leading(t1) use_nl(t2)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

select /*+leading(t1) use_nl(t2)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id
   and t1.object_id in (10, 11, 12);
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

select /*+leading(t1) use_nl(t2)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id
   and t1.object_id =10;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));  

select /*+leading(t1) use_nl(t2)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id
   and t1.object_id =99999; 
嵌套循环
驱动表的顺序对性能的影响(看Buffers),大表驱动好,还是小表驱动好?
select /*+leading(t1) use_nl(t2)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

select /*+leading(t2) use_nl(t1)*/count(*)
  from test1 t1, test2 t2
 where t1.object_id = t2.object_id;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
驱动表的顺序对性能的影响

相关文章:

  • 2021-08-10
  • 2021-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-12-23
  • 2022-12-23
  • 2021-05-05
猜你喜欢
  • 2022-12-23
  • 2021-08-29
  • 2022-01-25
  • 2022-12-23
  • 2022-02-14
  • 2021-09-07
  • 2021-11-10
相关资源
相似解决方案