在第OPTIMIZER_INDEX_COST_ADJ部分中,我讨论了此参数如何通过按参数中表示的百分比“调整”总成本来显著影响与索引相关的执行路径的总体成本。例如,如果OPTIMIZER_INDEX_COST_ADJ值为 25,则索引访问路径的成本将减少到仅计算总数的 25%。

降低使用索引的有效成本显然会增加 CBO 选择索引的可能性,而不是全表扫描 (FTS)。当 CBO 未使用适当的索引时,这可能是一件好事。但是,如果这导致CBO选择了不适当的指数而不是更有效的FTS,那也可能是一件非常糟糕的事情......

通常有 3 种不同的方法来设置此参数。

方法一:将其设置为一个非常低的值,以便索引通常优先于 FTS。

这是一个很常见的建议。我希望每次我读到有人建议设置(或进一步减少)OPTIMIZER_INDEX_COST_ADJ一个非常低的值时,我都会得到一美元,以便特定的SQL片段开始使用索引。经典示例就在这里,有人建议将此参数设置为 12,以便让 CBO 使用索引。

为什么是 12 ?

也许是因为他们曾经有一个使用FTS而不是索引的SQL语句,并且将OPTIMIZER_INDEX_COST_ADJ设置为12使得CBO使用索引,因此将其设置为12显然是有效的,对吧?也许是因为他们出生在12日和12日一直是一个幸运的数字?谁知道呢?

但是,在没有特定原因的情况下简单地将OPTIMIZER_INDEX_COST_ADJ设置为较低的值通常是一件非常危险且不明智的事情。

记住这个关键点。OPTIMIZER_INDEX_COST_ADJ的目的不是确保指数优于富时指数。正如我在第一部分中所讨论的,目的是更准确地反映与索引访问路径关联的单个块 I/O 的实际成本与与 FTS 关联的多块 I/O 的实际成本之间的任何差异。因此,OPTIMIZER_INDEX_COST_ADJ应尽可能准确地反映任何此类差异,以便比较成本是真实的,并反映实际的相应成本。

该参数应尝试在指数的使用和 FTS 之间设置一个公平的竞争环境,而不仅仅是简单地调整有利于指数的因素。

将此参数设置为任意小的数字确实可能使特定的 SQL 语句在以前使用 FTS 时突然使用索引。也许现在使用索引可能确实会从中受益。

但是,此参数是全局参数,因为它会影响整个系统或会话的所有SQL 语句。虽然它确实可能使CBO在一些地方适当地使用索引,但不幸的是,如果设置不正确,它也可能会使CBO开始不恰当地使用索引。也许使用FTS实际上是要走的路,实际上是其他SQL语句最有效和成本更低的选择,但是通过使此参数如此之低以至于偏爱索引,它可能会突然使CBO选择效率更低且成本更高的索引。

因为当然有很多很多情况下,FTS实际上是所需的访问方法,因为它只是更便宜,成本更低的替代方案。事实上,选择不当的索引,使用而不是更便宜的FTS的索引实际上对整体数据库性能的损害远远大于由不适当的FTS造成的潜在损害。

考虑一下。在最坏的情况下,FTS 将生成的最大逻辑(或物理)I/O 数是表中的块数。实际 I/O 的数量可能远远少于表中的块数,因为 FTS 通常会执行多块 I/O,并一次读取较大“块”中的所有表块。但是,即使配置不当的db_file_multiblock_read_count有效地设置为1,FTS的另一个巨大优势是Oracle只需要访问一次特定的块。因此,一次读取一个表块,实际上与FTS一样糟糕。

在一个 10,000,000 行的表中,比如 100,000 个表块,对于 FTS,最大 LIO 数实际上是 100,000

但是,索引范围扫描的更糟糕的情况可能要糟糕得多。在集群较差的索引中,实际上可能不仅要读取索引结构中的每个叶块(再加上几个分支块才能到达感兴趣的第一个索引叶块),还要执行与表中行一样多的逻辑(或物理)I/O。

在一个 10,000,000 行的表中,如果该表位于 100,000 个表块中,例如相应索引中有 25,000 个叶块,对于索引范围扫描,最大 LIO 数可能高达 10,025,003,比 FTS 对应的更坏情况场景差 100 倍。当然,请记住,假设db_file_multiblock_read_count设置为合理的水平,FTS的实际LIO实际上可能比最坏的情况少得多。因此,可以想象,它可能比相应的FTS差1000倍。

您看到的索引扫描可能需要多次访问特定的表块,因为表中的索引数据可能随机分布在整个表中(换句话说,索引具有非常糟糕的聚类因素),因此在索引范围扫描期间仅在不同时间访问表块中的单个行。读取每个索引条目时,相应的 rowid 会不断引用与最近访问的表块不同的表块。如果特定表块包含 100 行,则在索引范围扫描操作期间,索引范围扫描可能需要访问同一块多达 100 次。

一个非常简单的例子来说明这一点。

在这里,我创建了一个包含 10,000,000 行的表,其 ID 列的值在整个表中随机分布:

SQL>创建表鲍伊(ID号,文本varchar2(80));

已创建表。

SQL>插入到鲍伊选择ceil(dbms_random.value(0,10000)),“这只是一段旨在使行具有合理大小的文本”,从双重连接按级别< = 1000000;

已创建 10000000 行。

SQL>提交;

提交完成。

 

I now create an index on this ID column and collect 100% accurate statistics:

SQL> create index bowie_id on bowie(id);
 
Index created.
 
SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>’BOWIE’, estimate_percent=>null, cascade=>true, method_opt=> ‘FOR ALL COLUMNS SIZE 1′);
 
PL/SQL procedure successfully completed.

 

I now run a select statement that selects just 10%of rows in the table. Many would expect the index to be used by the CBO as 90% of all rows are of no interest:

SQL>从id介于1和1000之间的鲍伊中选择*;

选中1000873行。

已用: 00:01:49.75
执行计划
———————————————————-
计划哈希值: 1845943507

—————————————————————————
|编号 |操作|名称|行|字节|成本 (%CPU)|时间|
—————————————————————————
|0 |选择对账单| |100万|6900万|22068 (2)|00:01:51 |
|*1|表访问已满|鲍伊|100万|6900万|22068 (2)|00:01:51 |
—————————————————————————

谓词信息(由操作 id 标识):
—————————————————

1 – 过滤器(“ID”<=1000 和“ID”>=1)

 

统计信息
———————————————————-
1 递归调用
0 数据库块获取
108986一致获取
108672物理读取
0 重做大小
8920558 字节通过 SQL*Net 发送到客户端
2596 字节通过 SQL*Net 从客户端
接收 202 SQL*Net 往返客户端
0 排序(内存)
0 排序(磁盘)
1000873处理行

 

但是,我们注意到CBO实际上选择了FTS,并且查询在近1分50秒时有点慢。

嗯,我知道,让我们把OPTIMIZER_INDEX_COST_ADJ参数改成一个非常低的数字,看看CBO现在是否决定“做正确的事情”并使用索引。

我要选择的值,uuuummmmm让我想想,哦,我知道,让我们使用2的值,因为我昨天在足球中打进了2个进球,2对我来说一直有点幸运。这应该可以解决问题,让我们让指数看起来只有其原始成本的2%,并真正给这个指数一个很好的机会被CBO选中。

SQL>更改会话集optimizer_index_cost_adj = 2;
会话已更改。

SQL>从id介于1和1000之间的鲍伊中选择*;

选中1000873行。

已用: 02:16:44.42

执行计划
———————————————————-
计划哈希值:4117205494

—————————————————————————————-
|编号 |操作|名称|行|字节|成本 (%CPU)|时间|
—————————————————————————————-
|0 |选择对账单| |100万|6900万|19970 (1)|00:01:40 |
|1 |按索引 ROWID 进行表访问|鲍伊|100万|6900万|19970 (1)|00:01:40 |
|*2|索引范围扫描|BOWIE_ID |100万| |42 (0)|00:00:01 |
—————————————————————————————-

 

谓词信息(由操作 id 标识):
—————————————————

2 – 访问(“ID”>=1 和“ID”<=1000)

 

统计信息
———————————————————-
1 递归调用
0 db 块获取
998652一致获取
916134 物理读取

0 重做大小
5034208 字节通过 SQL*Net 发送到客户端
2596 字节通过 SQL*Net 从客户端
接收 202 SQL*Net 往返客户端
0 排序(内存)
0 排序(磁盘)
1000873行已处理

 

好消息是,CBO现在正在使用该指数。

然而,坏消息是,经过的时间已经从1分50秒变成了2小时16分44秒

将OPTIMIZER_INDEX_COST_ADJ更改为任意“低”值突然导致一些关键的SQL语句现在执行得令人震惊。

请记住,这是针对仅返回10%数据的查询...

为什么现在情况如此糟糕?

由于 ID 值在整个表中随机分布,因此 ID 列上的索引具有可怕的/可怕的聚类因子。因此,在执行索引范围扫描时,Oracle 被迫使用每个新索引键不断访问不同的表块,这可能会一次又一次地返回到同一个表块。逻辑I/O的数量将是巨大的,并且比FTS的数量显着增加,但由于OPTIMIZER_INDEX_COST_ADJ值,CBO实际上只花费了所有这些I / O的2%。

它给该指数带来了不公平的优势,并严重低估了与使用该指数与富时指数相关的真实成本。

更糟糕的是,由于它使用索引,所有这些大量块可能会导致缓冲区缓存中出现更多性能问题,因为通过FTS访问的块缓存这些块的方式更有利。

我还可以补充一点,在这个特定示例中,CBO 使用的是 CPU 成本模型(我将在后面讨论)。因此,即使CBO理论上已经对单块与多块I / O的比较成本有了一个好主意,OPTIMIZER_INDEX_COST_ADJ参数仍然具有影响。

因此,简单地将OPTIMIZER_INDEX_COST_ADJ参数设置为任意低的值是一种非常危险的调整技术,因为 CBO 存在选择不适当的索引访问路径的风险。

接下来,我将讨论如何将OPTIMIZER_INDEX_COST_ADJ参数设置为更合适的值的两种更好的技术。

 

参考至:​​http://richardfoote.wordpress.com/2009/07/22/the-cbo-and-indexes-optimizer_index_cost_adj-part-ii/​​​如有错误,欢迎指正