`
kiddwyl
  • 浏览: 403476 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

CBO学习笔记

阅读更多
cost of b-tree access

这一节我们看看CBO是如何来计算最普通的b-tree索引的访问的cost的。

我们知道B-tree索引的结构是一个树状结构,索引中包括root block,branch block, leaf block,这些结构分别相当与树的根,茎和叶。
当我们使用一个索引的时候,我们首先访问索引的root block,确定需要访问大branch block。
然后选择需要访问的branch blcok,通过访问branch block,我们知道符合我们条件的第一个leaf block,可以叫做start key
我们从start key访问leaf block的列表,知道找到我们需要的最后一个leaf block--->stop key。
访问符合条件的leaf block的时候,并视情况而定要不要访问相应的表。我们知道leaf block上的信息包括被索引的列的值和在表中相应行的rowid,所以如果我们只需要查询被索引的列上的信息,我们就无需访问index对应的表,否则就需要访问表。

我们知道CBO的Cost包括IO的cost和CPU的cost,而Oracle认为每一个single block access都是一个真实的IO,所以如果我们启动CPU的cost,访问索引的Cost就是访问索引的Block个数。

访问索引的cost可以用下面的公式来表示:

cost = blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)

第一行的blevel是指为了访问一个leaf block,我们需要访问的block的个数(不包括leaf block本身),Oracle总是维护一个Balanced B-trees,也就是说不管访问哪桓鰈eaf block,我们需要访问的中间block(包括root block和branch block)的个数都是一样多的。这个blevel也常常被称为索引的高度。

第二行是指我们的查询需要访问的leaf block的个数。

第三行是指为了获取我们需要的所有数据,我们需要访问的表的block的个数。

我们来做个实验来验证上面的公式。

1) 建表,建索引并收集统计数据:
create table t1
nologging
as
select
trunc(dbms_random.value(0,25)) n1,
rpad('x',40) ind_pad,
trunc(dbms_random.value(0,20)) n2,
lpad(rownum,10,'0') small_vc,
rpad('x',200) padding
from
all_objects
where
rownum <= 10000
;

create index t1_i1 on t1(n1, ind_pad, n2)
nologging
pctfree 91
;

begin
dbms_stats.gather_table_stats(
user,
't1',
cascade => true,
estimate_percent => null,
method_opt => 'for all columns size 1'
);
end;
/

我们查询表和索引的统计信息:
select
table_name,
blocks,
num_rows
from user_tables
where table_name = 'T1'
;

TABLE_NAME BLOCKS NUM_ROWS
------------------------------ ---------- ----------
T1 371 10000

(HWM下有371个block,一共有10000行)

select
num_rows, distinct_keys,
blevel, leaf_blocks, clustering_factor,
avg_leaf_blocks_per_key, avg_data_blocks_per_key
from
user_indexes
where table_name = 'T1'
and index_name = 'T1_I1'
;

NUM_ROWS DISTINCT_KEYS BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY
---------- ------------- ---------- ----------- ----------------- ----------------------- -----------------------
10000 500 2 1112 9745 2 19

(索引上一共有10000行,
索引的列上的distinct key是500个,
这个索引的blevel=2,也就是说有一个level 2的root block和多个level 1的branch block,
一共有1112个leaf block,
cluster_factor=9745,
平均的说,索引上每一个distinct key占用2个索引leaf block,
平均的说,索引上每一个distinct key占用19个table block。

这里简单解释一下几个概念:
1,distinct key,
因为我们被索引的列是n1, ind_pad,n2。其中n1有25个不同的值,ind_pad的值总是一样的,n2有20个不同的值,所以索引上distinct key=25*1*20=500。

2,cluster_factor,
这是一个判断表上的数据分布是不是和索引一样有序的一个值,它的取值范围在表的block的个数和表的行数之间,越接近block的个数说明表的数据分布越有序(和索引一样有序),越是接近行数越说明表上的数据分布是混乱的。
可以这样理解cluster_factor,当我们按照索引上的顺序访问表的时候,每当我们需要的数据不在当前block上的时候,我们就要“跳到”其他block上进行访问,如果表上的数据排列和索引是完全一样的话,我们跳的次数等于表的Block的个数,如果是另一个极端,表的数据分布极其的混乱,我们访问每一行数据都要跳一次,那我们最后跳的次数就等于行数。这里跳的次数就是cluster_factor。从这个角度来说,如果我们访问索引之后还有相应的访问表,假设我们访问了X%的索引block,那我们需要访问表的IO次数应该是X% * cluster_factor,取决于X和cluster_factor的大小,X% * cluster_factor有可能比表的所有block的个数还有多(当然这种情况下很有可能CBO已经选择全表扫描了),因此cluster_factor对于计算索引访问的Cost是很重要的,经常可以用来回答类似与“为什么我们索引没有被使用这类问题”。

下面是一个cluster_factor=10的图示:


需要明确的是根据公式
cost = blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)
的第三个部分我们知道cluster_factor越小访问索引COST的就越小(当然是在访问完索引后不得不也访问表的情况下),但cluster_factor大小其实不是衡量索引好坏的标准,这个值是因为表本身的数据分布和索引排列数据顺序不符所造成的,即使我们rebuild索引,cluster_factor也不会改变(只要索引的定义没有改变)。

实验1

 

先做个简单的实验:
select
small_vc
from
t1
where
n1 = 2
and ind_pad = rpad('x',40)
and n2 = 3
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=25 Card=20 Bytes=1160)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=25 Card=20 Bytes=1160)
2 1 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=5 Card=20)

我们看到索引本身RANGE SCAN的Cost=5,Card=20, 最后的总cost是25,也就是table的访问cost是20,按照我们个公式:

cost = blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)
我们算一下:
blevel =2
leaf_blocks=1112
effective index selectivity = (selectivity of n1)*(selectivity of ind_pad)*(selectivity of n2)= (1/25)*1*(1/20)=1/500=0.002
clustering_factor=9745
effective table selectivity在这里其实是和索引的effective index selectivity一样的=(selectivity of n1)*(selectivity of ind_pad)*(selectivity of n2)= (1/25)*1*(1/20)=1/500=0.002

放在一起我们得到
cost = 2+ ceil(1112*0.002) + ceil(9745*0.002)=2+ 3 + 20 = 25。
其中2+3是索引访问本身的cost,20是表的访问cost。

从这个例子里我们发现表本身的cost占绝大多数,这是因为这个索引的clustering_factor很大。很多时候我们都希望重建索引可以大大提高提高索引的效率,但要记住重建索引只能减少leaf_blocks的值(能减少多少取决于索引的具体情况),但实际上有时候真正影响更大的是clustering_factor(当然有时候是leaf_blocks,取决于具体情况)。

实验2

 

我们在条件里面加一个范围查询:

select
/*+ index(t1) */
small_vc
from
t1
where
n1 = 2
and ind_pad = rpad('x',40)
and n2 between 1 and 3
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=93 Card=82 Bytes=4756)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=93 Card=82 Bytes=4756)
2 1 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=12 Card=82)

首先我们加了一个/*+ index(t1) */强制使用索引,否则这个查询会选择full table scan,因为如果你实验一下或按照我们以前介绍的方法算一下,这个表的full table scan的cost只有58。
我们再算一下cost:

selectivity (n2 between 1 and 3) =
required range / total range + 1/num_distinct + 1/num_distinct =
(3 – 1) / (19 – 0) + 1/20 + 1/20 = 0.205263

Effective index selectivity = 1 * 0.04 * 0.205263 = 0.0082105
Effective table selectivity = 1 * 0.04 * 0.205263 = 0.0082105
Cost = 2 + ceiling(1,112 * 0.0082105) + ceiling(9,745 * 0.00082105) = 12 (index的扫描cost) + 81(table的扫描cost) = 93

实验3

 

我们修改一下上面的SQL:

alter session set "_optimizer_skip_scan_enabled"=false;
select
/*+ index(t1) */
small_vc
from
t1
where
n1 between 1 and 3
and ind_pad = rpad('x',40)
and n2 = 2
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=264 Card=82 Bytes=4756)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=264 Card=82 Bytes=4756)
2 1 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=184 Card=1633)

我们这里修改_optimizer_skip_scan_enabled为false,这样CBO就会使用Cost更低的Index skip scan。

这次我们发现总的cost相当高,但是按照公式计算:
selectivity (n1 between 1 and 3) =
required range / total range + 1/num_distinct + 1/num_distinct =
(3 – 1) / (24 – 0) + 1/25 + 1/25 = 0.163333

Effective index selectivity= 0.163333*1*0.05=0.0081667
Effective table selectivity = 0.163333*1*0.05=0.0081667
cost= 2 +
ceil(0.0081667 * 1,112)
ceil(0.0081667 * 9745)
= 92

我们的公式结果和explain plan里的显示差很多,这是为什么呢?
原因在于复合索引存储的方式,因为复合索引存储数据的时候首先是按照第一个被索引的列来排序的,也就是说把n1=0的放在,然后是n1=1的放在一起,然后n1=2......
类似于(这里只是大概描述一下概念,不一定准确):
第一组:
n1=0
{ind_pad='x ' (n2=0, n2=1,n2=2....n2=19)}

第二组:
n1=1
{ind_pad='x ' (n2=0, n2=1,n2=2....n2=19)}

.....

第二十五组:
n1=24
{ind_pad='x ' (n2=0, n2=1,n2=2....n2=19)}


所以当我们第一个条件是n1 between 1 and 3的时候,如果没有开启从oracle9i里面开始引进的index skip scan,其实CBO是很郁闷的,因为他要扫描很多block才能获得n1 between 1 and 3的结果,实际上在这种情况下,CBO忽略了后面的ind_pad和n2的查询条件,而是查询了所有n1 between 1 and 3的leaf block再从中选出符合ind_pad和n2的查询条件的leaf block,所以这里
Effective index selectivity = index selectivity of n1 = 0.163333,但是Effective table selectivity还是= 0.163333*1*0.05=0.0081667,这样我们再次计算:

cost= 2 +
ceil(0.163333 * 1,112)
ceil(0.0081667 * 9745)
= 2 + 182 + 80 = 264

在Oracle9i的plan_table里面引入了两个新的列:
SQL> desc plan_table
Name Null? Type
----------------------------------------- -------- ----------------------------
STATEMENT_ID VARCHAR2(30)
TIMESTAMP DATE
REMARKS VARCHAR2(80)
OPERATION VARCHAR2(30)
OPTIONS VARCHAR2(255)
OBJECT_NODE VARCHAR2(128)
OBJECT_OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(30)
OBJECT_INSTANCE NUMBER(38)
OBJECT_TYPE VARCHAR2(30)
OPTIMIZER VARCHAR2(255)
SEARCH_COLUMNS NUMBER
ID NUMBER(38)
PARENT_ID NUMBER(38)
POSITION NUMBER(38)
COST NUMBER(38)
CARDINALITY NUMBER(38)
BYTES NUMBER(38)
OTHER_TAG VARCHAR2(255)
PARTITION_START VARCHAR2(255)
PARTITION_STOP VARCHAR2(255)
PARTITION_ID NUMBER(38)
OTHER LONG
DISTRIBUTION VARCHAR2(30)
CPU_COST NUMBER(38)
IO_COST NUMBER(38)
TEMP_SPACE NUMBER(38)
ACCESS_PREDICATES VARCHAR2(4000)
FILTER_PREDICATES VARCHAR2(4000)

这里access_predicates列出访问索引是被使用了的条件,filter_predicates列出被忽略了的条件。


实验4

 

我们知道8i开始有一个和索引有关的参数:OPTIMIZER_INDEX_CACHING,官方关于这个参数的解释是:
OPTIMIZER_INDEX_CACHING lets you adjust the behavior of cost-based optimization to favor nested loops joins and IN-list iterators.
The cost of executing an index using an IN-list iterator or of executing a nested loops join when an index is used to access the inner table depends on the caching of
that index in the buffer cache. The amount of caching depends on factors that the optimizer cannot predict, such as the load on the system and the block access patterns of different users.

也就是说,这个参数仅仅影响nested loops和in list中的index的使用,我们在其他的部分介绍nested loops,现在现看看对In-list的影响:

默认情况optimizer_index_caching = 0

select
/*+ index(t1) */
small_vc
from
t1
where n1 = 5
and ind_pad = rpad('x',40)
and n2 in (1,6,18);


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=68 Card=60 Bytes=3480)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=68 Card=60 Bytes=3480)
3 2 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=9 Card=60)


alter session set optimizer_index_caching = 25;

select
/*+ index(t1) */
small_vc
from
t1
where n1 = 5
and ind_pad = rpad('x',40)
and n2 in (1,6,18)
;


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=65 Card=60 Bytes=3480)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=65 Card=60 Bytes=3480)
3 2 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=6 Card=60)


alter session set optimizer_index_caching = 50;

select
/*+ index(t1) */
small_vc
from
t1
where n1 = 5
and ind_pad = rpad('x',40)
and n2 in (1,6,18)
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=63 Card=60 Bytes=3480)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=63 Card=60 Bytes=3480)
3 2 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=4 Card=60)

 

alter session set optimizer_index_caching = 75;

select
/*+ index(t1) */
small_vc
from
t1
where n1 = 5
and ind_pad = rpad('x',40)
and n2 in (1,6,18)
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=61 Card=60 Bytes=3480)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=61 Card=60 Bytes=3480)
3 2 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE) (Cost=2 Card=60)


alter session set optimizer_index_caching = 100;

select
/*+ index(t1) */
small_vc
from
t1
where n1 = 5
and ind_pad = rpad('x',40)
and n2 in (1,6,18)
;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=59 Card=60 Bytes=3480)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=59 Card=60 Bytes=3480)
3 2 INDEX (RANGE SCAN) OF 'T1_I1' (NON-UNIQUE)

不是很清楚具体是怎么计算了,看上来好像是只影响Index部分的cost,比如原来的cost是9,当设置optimizer_index_caching =75之后,那么新的cost= trunc(9*0.25)=2。

8i引入的和index有关的另一个重要的参数是optimizer_index_cost_adj,我们知道在8i里面CBO是不知道单块读和多块读的区别的,而这个参数就是为了弥补这个问题,默认值100是说单块读和多块读的cost一样,当我们调小这个参数值以后,CBO认为单块读比多块读的cost小,否则如果调大,CBO会认为单块读比多块读cost大。optimizer_index_cost_adj的问题在于它并不是影响CBO对多块读的cost,而是影响CBO对单块读的cost,这其实是个有问题的逻辑,因为合理的思维是应该调整多块读的cost,所有有的时候可能会导致CBO本来选择了一个较好的索引,但调整了optimizer_index_cost_adj后会选择一个较差的索引。从这个角度来说9i的system statistics就是调整多块读的cost,所以system statistics是更加合理的,而且system statistics是可以在真实环境中收集信息的,所以也比我们估计optimizer_index_cost_adj来的合理。

分享到:
评论

相关推荐

    Oracle CBO 学习笔记之(1) : 深入理解Oracle Hash Join的代价模型及其执行流程

    在这个学习笔记中,我们将深入探讨Oracle中的Hash Join操作,这是一种重要的联接(JOIN)类型,尤其在处理大数据量时能展现高效的性能。 Hash Join的基本原理是通过构建一个哈希表来实现两个表的连接。首先,Oracle...

    最牛逼的Oracle 11g OCP学习笔记

    以下是对这份"最牛逼的Oracle 11g OCP学习笔记"中的关键知识点的详细阐述: 一、Oracle 11g基础知识 Oracle 11g引入了许多新特性,如自动内存管理、数据屏蔽、实时应用集群(RAC)、闪回数据库等。其中,自动内存管理...

    Oracle10g_学习笔记.zip

    6. SQL优化器改进:包括Cost-Based Optimizer(CBO)和Rule-Based Optimizer(RBO),使得SQL查询更高效。 7. 数据库审计:增强了安全性和合规性,支持详细的审计记录。 二、安装与配置 Oracle 10g的安装涉及多个...

    oracle9ir2 performance tuning guide and reference 学习笔记大白话

    - **基于成本的优化器(CBO)**:CBO考虑了多种因素,包括表的大小、索引的存在与否、数据分布情况等,从而计算出每种可能的执行路径的成本,并选择成本最低的那个作为执行计划。这种方式更加灵活和智能,能够适应...

    HTML入门笔记2源代码

    这篇"HTML入门笔记2源代码"提供了关于a、img、table和form等基础标签的使用示例,非常适合初学者掌握和实践HTML的基本语法。 一、a标签 a标签是HTML中的超链接标签,用于创建链接到其他页面、文件、邮件地址或者...

    此书从oracle入门开始,讲解浅显易懂,很难得的资料!

    - **写笔记**:记录学习过程中重要的知识点和思考,有助于巩固记忆。 - **做实验**:通过实践来验证理论知识的有效性,加深理解。 - **再次思考与写笔记**:重复这个过程,直到完全理解为止。 **2.2 解决问题的态度...

    oracle培训18天笔记

    Oracle数据库系统是世界上最广泛使用的数据库管理系统之一,...这18天的笔记内容全面覆盖了Oracle的主要知识点,通过深入学习,可以建立起对Oracle数据库系统的全面认识,为实际工作中的数据库管理与维护打下坚实基础。

    oracle培训18天笔记

    3. 优化器:理解CBO(成本基优化器)和RBO(规则基优化器),以及如何影响查询执行计划。 第十一天至第十三天:备份与恢复 1. 数据库备份:完整备份、增量备份和差异备份的原理与实现。 2. 灾难恢复:RMAN(恢复...

    Oracle数据库笔记-JackChiang.docx

    Oracle数据库是全球最广泛...Jack Chiang的笔记可能涵盖了以上部分或全部内容,对于学习和理解Oracle数据库的运作机制和管理技巧具有很高的参考价值。通过深入阅读和实践,你可以成为一名熟练的Oracle数据库管理员。

    oracle学习资料集

    这个压缩文件可能包含关于Oracle查询优化的深度内容,讲解了Oracle的CBO(Cost-Based Optimizer)如何根据成本选择执行计划。可能涉及索引、统计信息、执行计划分析、SQL优化技巧等内容。 5. **oracle管理课件** ...

    oracle核心技术读书笔记一附件1

    以上是Oracle数据库核心技术的概览,实际应用中还需要结合具体场景和需求,深入学习和理解这些概念,才能更好地管理和优化Oracle数据库。通过阅读和理解这些读书笔记,可以提升对Oracle数据库的理解和操作能力。

    2018 Oracle DBA :工作笔记-新特性、性能优化与运维

    - 对于CBO(成本为基础的优化器)无法优化的情况,DBA需要深入分析执行计划,可能需要手动调整索引或统计信息。 4. **Oracle压缩技术**: - Oracle提供了多种压缩选项,包括基础表压缩、压缩数据的修改以及针对...

    《Pro Oracle SQL》 读书笔记--Chapter 6--6.2 Execution Plans--之四

    - **CBO(Cost-Based Optimizer)与RBO(Rule-Based Optimizer)**:CBO根据统计信息和成本估算选择执行计划,RBO则基于预定义的规则。 - **绑定变量与硬解析**:使用绑定变量可以减少硬解析次数,提高性能。 - *...

    涂抹oracle源代码

    这部分的学习需要理解CBO(Cost-Based Optimizer)的工作原理,以及如何通过统计信息来评估查询计划的成本。 再者,Oracle的存储引擎部分,包括表空间、数据块、段、区等概念,以及如何进行物理存储和逻辑存储的...

    Mysql源码整理)_news4ep_mysql源码_MYSQL_

    本资源包“Mysql源码整理_news4ep_mysql源码_MYSQL_”是数据库学习者在研究MySQL源码过程中的笔记和资料集合,旨在帮助用户更深入地了解MySQL的内部工作机制。 MySQL源码的学习可以从以下几个关键方面展开: 1. **...

    oracle文档资料

    这份文档可能是个人或集体学习Oracle数据库时的笔记总结,可能包括安装配置、SQL语法、数据类型、表空间管理、存储过程、触发器、事务处理等内容。通过阅读,读者可以对Oracle的基本操作和概念有初步理解。 2. **...

    Hadoop_Hive_Project:NYU CSCI-GA.3033-003的课程项目

    4. **优化查询执行**:Hive通过查询优化器(如CBO - Cost-Based Optimization)改进查询性能,选择最佳的执行计划。 **Jupyter Notebook**: Jupyter Notebook是一个交互式笔记本环境,广泛用于数据分析、机器学习...

Global site tag (gtag.js) - Google Analytics