Oracle里的优化器
优化器的目的是按照一定的判断原则来得到它认为的目标sql在当前情形下最高效的执行路径。
基于成本的优化器CBO
CBO:对目标sql执行所要耗费的cpu,i/o和网络资源的一个估算值
在解析目标sql时,首先会对目标sql执行查询转换,接下来,cbo会计算执行完查询转换这一步后得到的等价改写sql的诸多可能的执行路径的成本,然后从中选择成本最小的一条来得到执行计划,然后根据此计划执行sql。
1 集的势:cardinality 指定集合所包含的记录数
2 可选择率:施加条件的记录数/未施加条件的记录数
Emp :empno 主键 SQL> set autot trace SQL> select * from emp where empno=7369; Execution Plan Plan hash value: 2949544139 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 37 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 37 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO"=7369) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 850 bytes sent via SQL*Net to client 457 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed exec dbms_status.set_table_stats(owner=>'SCOTT',tabname=>'EMP',numrows=>10000000,no_invalidate=>false); exec dbms_status.set_index_stats(owner=>'SCOTT',tabname=>'PK_EMP',numlbks=>100000,no_invalidate=>false);
指定修改表emp的统计信息
CBO会根据反映目标sql中相关对象的实际数据量、实际数据分布等情况的统计信息来决定其执行计划
3 可传递性
简单谓词传递
create table t1 (c1 number ,c2 varchar2(10)); create table t2 (c1 number ,c2 varchar2(10)); create index idx_t2 on t2(c1); 插入一些数据,并收集统计信息 SQL> select t1.c1,t2.c2 from t1,t2 2 where t1.c1=t2.c1 and t1.c1=1; Execution Plan Plan hash value: 2253255382 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 4 | 1 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 1 | 17 | 4 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL | T1 | 1 | 13 | 3 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | IDX_T2 | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter("T1"."C1"=1) 4 - access("T2"."C1"=1)
连接谓词传递
Where t1.c1=t2.c1 and t2.c1=t3.c1 cbo可能会在这个谓词中额外的加上t1.c1=t3.c1
外连接谓词传递
SQL> select t1.c1,t2.c2 from t1,t2 2 where t1.c1=t2.c1(+) and t1.c1=1; Execution Plan Plan hash value: 925641968 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 4 (0)| 00:00:01 | | 1 | NESTED LOOPS OUTER | | 1 | 17 | 4 (0)| 00:00:01 | |* 2 | TABLE ACCESS FULL | T1 | 1 | 13 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 4 | 1 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | IDX_T2 | 1 | | 0 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("T1"."C1"=1) 4 - access("T2"."C1"(+)=1)
CBO的局限性:CBO会默认目标sql语句where条件中出现的各个列之间是独立的,没有关联联系
CBO会假设所有的目标sql都是单独执行的,并且互不干扰
CBO对直方图统计信息有诸多限制
CBO在解析多表连接sql时,可能会漏选正确的执行计划
优化器的基础知识
优化器的模式
First_rows_n(n=1,10,100,1000)此时cbo在计算该sql的各条执行路径的成本值时的侧重点在于以最快的响应速度返回头n=1,10,100,1000条记录
First_rows走索引全扫描,能够避免排序
All_rows:Oracle 10g默认的优化器模式,侧重点在于最佳的吞吐量(最小的系统io,cpu资源的消耗量)
访问数据的方法:
访问表:全表扫描,rowid扫描
全表扫描:在Oracle访问目标表里的数据时,会从该表所占用的第一个区(extent)的第一个块(block)开始扫描,一直扫描到该表的高水位线(HWM),然后在根据过滤条件返回数据
全表扫描,Oracle做全全表扫描操作时使用多块读,在目标表数量不大时效率非常高,随表数据量的增加成本而增加,在目标sql执行时时间不会太稳定,不可控
Rowid扫描:rowid和Oracle中数据块里的行记录一一对应
根据用户sql中输入的rowid来返回数据
通过index扫描,根据索引扫描得到rowid在回表去访问相应的行记录
访问索引的方法:
每个索引分支块都会有两种类型的指针,一种lmc,另一种是索引分支块的索引行记录所记录的指针,
索引列键值列不一定就是完整的被索引键值,可能只是被索引键值的前缀,只要Oracle能通过这个索引前缀能区分相应的索引分支或叶子块就行
Oracle访问b树索引的操作都必须从根节点开始,都会经历一个从根节点到分支块再到叶子块的过程
对于唯一性b树索引,rowid存储在行头,此时Oracle不需要额外存储该rowid的长度
对于非唯一性b树索引,rowid被当做额外的列与被索引的建值列一起存储,此时Oracle还要存储其长度,在同等条件下,unique index比非唯一索引要节省空间。Oracle里的索引叶子块是左右互联
B树索引的优势:
1 所有的索引叶子块都在同一层,他们到根节点的距离相同,访问叶子块的任何一个索引键值所花费的时间几乎相同
2 Oracle会保证索引的b树索引是自平衡的
3 通过b树索引访问表行记录的效率并不会随着表的数据的增加而显著降低,通过index访问数据的时间是可控制,基本稳定
常见的访问b树索引的方法
1 索引唯一性扫描
INDEX UNIQUE SCAN 使用于where的等值查询
2 索引范围扫描
INDEX RANGE SCAN 适用于所有类型的b树索引,当扫描的对象时唯一性索引,此时目标sql使用的了范围查询(<.between>)等,:扫描非唯一索引,对目标sql的where条件没有限制
alter system flush shared_pool
alter system flush buffer_cache
3 索引全扫描
INDEX FULL SCAN 适用所有类型的b树类型,指扫描目标索引所有叶子块的所有索引(并不一定需要扫描该索引的所有分支块),
默认情况Oracle在做索引全扫描只需要通过访问必要的分支块定位该索引最左边的叶子块的第一行索引,可以利用该索引叶子块之间的双向指针链表,从左到右依次访问
索引全扫描的执行结果也是有序的,并且按照该索引的索引键值来排序
通常情况下 索引全扫描 不能够并行执行,使用的单块读,不需要回表
对于b树索引,当所有索引键值列全为null值时不入索引,Oracle中能做索引全扫描的前提条件是目标索引至少有一个所有键值不能为null
4 索引快速全扫描
INDEX FAST FULL SCAN 适用所有类型的b树 ,需要扫描目标索引所有叶子块的所有索引行
索引全扫描与索引快速全扫描的区别:
索引快速全扫描只适合cbo
索引快速全扫描可以使用多块读,也可以使用并行
索引快速全扫描的结果不一定是有序的,
SQL> select /*+ index_ffs(emp PK_EMP)*/ empno from emp; 14 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 366039554 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 56 | 2 (0)| 00:00:01 | | 1 | INDEX FAST FULL SCAN| PK_EMP | 14 | 56 | 2 (0)| 00:00:01 |
5 索引跳跃式扫描
INDEX SKIP SCAN 适用于所有类型的复合b树索引,使那些在where条件中没有对目标索引的前导列指定查询条件但同时又对该索引的非前导列指定了查询条件的目标sql依然可以使用该索引
Oracle对该索引的前导列的所有distinct值做了遍历,在前导列唯一值较少的情况下,才会用到index skip scan
表连接 Inner join,left join,right join,full outer join Full join:先做左连接,在做又连接,然后两个结果做union 表连接方法 SMJ,NL,HJ 通常排序合并连接的执行效率不如哈希连接高,前者可以使用范围,哈希连接只使用等值连接 排序合并不适合oltp,有排序操作 NL:t1驱动表(外层),t2被驱动表(内层)。 驱动表对应的结果集较小,同时被驱动表的连接列上又有唯一性索引,执行效率非常高 Nl连接:可以实现快速响应 Oracle 11g改善了嵌套循环连接,引入向量IO SQL> select /*+ leading (t1) use_nl(t2) */t1.c1,t2.c2 from t1,t2 2 where t1.c1=t2.c1(+); Execution Plan ---------------------------------------------------------- Plan hash value: 925641968 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 4 (0)| 00:00:01 | | 1 | NESTED LOOPS OUTER | | 1 | 17 | 4 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL | T1 | 1 | 13 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 4 | 1 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | IDX_T2 | 1 | | 0 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("T1"."C1"="T2"."C1"(+)) 哈希连接:cbo,等值连接,参数HASH_JOIN_ENABLED,适合一个小表与大表关联,小表作为驱动表 SQL> select /*+ leading (t1) use_hash(t2) */ t1.c1,t2.c2 2 from t1,t2 3 where t1.c1=t2.c1(+); Execution Plan ---------------------------------------------------------- Plan hash value: 1823443478 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN OUTER | | 1 | 17 | 7 (15)| 00:00:01 | | 2 | TABLE ACCESS FULL| T1 | 1 | 13 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| T2 | 6 | 24 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T1"."C1"="T2"."C1"(+))