CBO(基于代价的优化器)是RBO(基于规则的优化器)的替代品,从9i开始oracle就建议用户使用RBO来进行SQL的优化。CBO大概的优化原理很简单,他通过对象上的统计信息来计算各个执行计划的代价,然后选择代价较小的执行计划来运行。所以对于CBO来说对象(比如表,索引)上的统计信息就显得十分的重要,不仅要有统计信息,还要保证统计信息是准确的,不准确的统计信息可能会带来灾难性的结果。那么oracle是怎么样利用这些统计信息呢。下面举个简单的例子帮助大家理解:
结果为空
我们创建了一张测试表sunwg,并且分析了表上的统计信息,但是我没有分析列id的统计信息。
从这个SQL的执行计划可以看出来,oracle认为这个SQL会查找出1条记录。事实上呢,实际表中ID = 1的记录也只有一条,那么oracle估计得很正确。Oracle真的这么聪明么?我们可以想象一下,如果仅仅告诉你一个表中有100条记录,然后问你在这个表中ID = 1的记录有几条,你能算得出来么?答案是肯定的,你不了解数据的分布情况,无法得知ID = 1的记录数量,所以oracle得到的这个ID = 1的结果是“蒙”的。那么我们接着看下面的例子。
这下oracle就彻底的露馅了,oracle估计ID > 1的结果是5行,但实际上应该是99条。其实这也不能怪oracle,因为你给他的信息实在是太少了,为了优化的正常进行他必须要估计一个大概基数值才可以进行代价的计算。
Where 条件 Oracle估计记录数 表中实际记录数 Oracle估算公式(猜想)
ID = 1 1 1 100 * 1%
ID > 1 5 99 100* 5%
ID >= 1 5 100 100* 5%
ID = 110 1 0 1
ID + 1 > 1 5 100 100* 5%
ID + 1 >= 1 5 100 100* 5%
ID + 1 > 1 AND ID + 1 > 1 5 100 100* 5%
ID > 1 AND ID < 50 1 48 100 *( 5%* 5%)
ID >80 OR ID <30 10 49 100*(5% + 5% - 5%* 5%)
从上面这个结果我们还看不出来到底oracle笨不笨,毕竟我们没有把完备的统计信息给他。下面我会统计表上列ID的信息,看看有了列ID上的统计信息后,oracle会有什么样子的表现。
这个时候已经可以看到列ID上的统计信息了,在重复做上面的测试可以得到下面的结果:
Where 条件 Oracle估计记录数 表中实际记录数 Oracle估算公式(猜想)
上面的结果虽然比没有分析列ID上的统计信息之前要准确了一些,但是和实际还是有一些差距的。
在CBO里面还有另外一个和基数对应的概念——选择率,他们之间的关系应该是:
基数 = 记录数 * 选择率
如果要计算基数的时候只要先算出大概的选择率,然后在和记录数相乘就可以得到基数信息。
那么选择率应该怎么计算呢?(以第二张表格中的数据为例说明)
有个地方我们需要特别留意一下,就是在收集列ID上的信息的时候。
analyze table sunwg compute statistics for columns id size 100;
在前面的例子里面我们使用的是SIZE 100,如果这个SIZE不同的话,那么分析出来的
结果也是不一样的。实际上我们是在收集列上的直方图信息,这个SIZE就是直方图的
BUCKET的数量,这个值对直方图信息的准确性有着很重要的影响。关于直方图的东
西我会在其他文章介绍的。
SQL> create table sunwg (id number);
Table created.
SQL> insert into sunwg select rownum from all_tables where rownum<101;
100 rows created.
SQL> commit;
SQL> analyze table sunwg compute statistics for table;
Table analyzed.
sql> select num_rows,blocks,empty_blocks,avg_space,chain_cnt,avg_row_len
from user_tables where table_name = 'sunwg';
num_rows blocks empty_blocks avg_space chain_cnt avg_row_len
---------- ---------- ------------ ---------- ---------- -----------
100 1 6 6978 0 6
sql>select num_distinct,raw_to_number(low_value),raw_to_number(high_value),density,num_buckets
from user_tab_columns where table_name='sunwg';
结果为空
我们创建了一张测试表sunwg,并且分析了表上的统计信息,但是我没有分析列id的统计信息。
SQL> set autot traceonly exp
SQL> select * from sunwg where id = 1;
Execution Plan
----------------------------------------------------------
Plan hash value: 459567752
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| SUNWG | 1 | 6 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------
从这个SQL的执行计划可以看出来,oracle认为这个SQL会查找出1条记录。事实上呢,实际表中ID = 1的记录也只有一条,那么oracle估计得很正确。Oracle真的这么聪明么?我们可以想象一下,如果仅仅告诉你一个表中有100条记录,然后问你在这个表中ID = 1的记录有几条,你能算得出来么?答案是肯定的,你不了解数据的分布情况,无法得知ID = 1的记录数量,所以oracle得到的这个ID = 1的结果是“蒙”的。那么我们接着看下面的例子。
SQL> select * from sunwg where id >= 1;
Execution Plan
----------------------------------------------------------
Plan hash value: 459567752
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 30 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| SUNWG | 5 | 30 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
这下oracle就彻底的露馅了,oracle估计ID > 1的结果是5行,但实际上应该是99条。其实这也不能怪oracle,因为你给他的信息实在是太少了,为了优化的正常进行他必须要估计一个大概基数值才可以进行代价的计算。
Where 条件 Oracle估计记录数 表中实际记录数 Oracle估算公式(猜想)
ID = 1 1 1 100 * 1%
ID > 1 5 99 100* 5%
ID >= 1 5 100 100* 5%
ID = 110 1 0 1
ID + 1 > 1 5 100 100* 5%
ID + 1 >= 1 5 100 100* 5%
ID + 1 > 1 AND ID + 1 > 1 5 100 100* 5%
ID > 1 AND ID < 50 1 48 100 *( 5%* 5%)
ID >80 OR ID <30 10 49 100*(5% + 5% - 5%* 5%)
从上面这个结果我们还看不出来到底oracle笨不笨,毕竟我们没有把完备的统计信息给他。下面我会统计表上列ID的信息,看看有了列ID上的统计信息后,oracle会有什么样子的表现。
SQL> analyze table sunwg compute statistics for columns id size 100;
Table analyzed.
sql>select num_distinct,raw_to_number(low_value),raw_to_number(high_value),density,num_buckets
from user_tab_columns where table_name = 'sunwg';
num_distinct raw_to_number(low_value) raw_to_number(low_value) density
------------ ------------------------ ------------------------- ---
100 1 100 0.01
这个时候已经可以看到列ID上的统计信息了,在重复做上面的测试可以得到下面的结果:
Where 条件 Oracle估计记录数 表中实际记录数 Oracle估算公式(猜想)
ID = 1 1 1 100 *1%
ID > 1 99 99 100*((100 – 1)/100)
ID >= 1 100 100 100*((100 – 1)/100 + 0.01)
ID = 110 1 0 1
ID + 1 > 1 5 100 100 * 5%
ID + 1 >= 1 5 100 100 * 5%
ID + 1 > 1 AND ID + 1 > 1 5100 100 * 5%
ID > 1 AND ID < 50 4848 100*((99/100)*(49/100))
ID >80 OR ID <30 4449 100*(20/100+29/100 - (20/100)*(29/100))
上面的结果虽然比没有分析列ID上的统计信息之前要准确了一些,但是和实际还是有一些差距的。
在CBO里面还有另外一个和基数对应的概念——选择率,他们之间的关系应该是:
基数 = 记录数 * 选择率
如果要计算基数的时候只要先算出大概的选择率,然后在和记录数相乘就可以得到基数信息。
那么选择率应该怎么计算呢?(以第二张表格中的数据为例说明)
例一:条件ID = 1
选择率 = 1/100 = 0.01
基数 = 100 * 0.01 = 1
例二:条件ID >= 1
选择率 = (100 – 1)/100 + 0.01 = 1
基数 = 100 * 1 = 100
例三:条件ID > 1 AND ID < 50
选择率 = ((100 – 1)/100) * ((50 – 1)/100)= 0.48
基数 = 100 * 0.48 = 48
关于多个查询的条件的时候有三个公式的:
P(A) AND P(B) = P(A)的选择率 * P(B)的选择率
P(A) OR P(B) = P(A)的选择率 + P(B)的选择率 - P(A)的选择率 * P(B)的选择率
NOT P(A) = 1 - P(A)的选择率
有个地方我们需要特别留意一下,就是在收集列ID上的信息的时候。
analyze table sunwg compute statistics for columns id size 100;
在前面的例子里面我们使用的是SIZE 100,如果这个SIZE不同的话,那么分析出来的
结果也是不一样的。实际上我们是在收集列上的直方图信息,这个SIZE就是直方图的
BUCKET的数量,这个值对直方图信息的准确性有着很重要的影响。关于直方图的东
西我会在其他文章介绍的。