Oracle - 了解no_index提示

时间:2011-01-20 17:44:09

标签: oracle query-optimization hint

我正在尝试了解no_index如何实际加速查询并且无法在线查找文档来解释它。

例如,我的查询运行极慢

select  * 
    from    <tablename>
    where   field1_ like '%someGenericString%' and 
            field1_ <> 'someSpecificString' and
            Action_='_someAction_' and 
            Timestamp_ >= trunc(sysdate - 2)

我们的一位DBA能够通过这样做显着加快速度

select  /*+ NO_INDEX(TAB_000000000019) */ * 
    from    <tablename>
    where   field1_ like '%someGenericString%' and 
            field1_ <> 'someSpecificString' and
            Action_='_someAction_' and 
            Timestamp_ >= trunc(sysdate - 2) 

我无法弄清楚为什么?我想弄清楚为什么这样可行,所以我可以看看我是否可以将它应用到另一个查询(这是一个连接)来加速它,因为它需要更长的时间才能运行。

谢谢!


**更新** 以下是我对示例中表格的了解。

  • 这是一个'分区表'
  • TAB_000000000019是表中的不是列
  • field1已编入索引

6 个答案:

答案 0 :(得分:9)

Oracle的优化器会对如何最好地运行查询做出判断,为此,它使用了大量有关表和索引的统计信息。例如,根据这些统计数据,它决定是否使用索引,或仅进行表扫描。

重要的是,这些统计数据并不是最新的,因为它们收集起来非常昂贵。如果统计信息是最新的,优化程序可以做出“错误”的决定,并且当执行表扫描实际上更快时,可能会使用索引。

如果DBA /开发人员知道这一点,他们可以向优化器提供提示(这是NO_INDEX是什么),告诉它不要使用给定的索引,因为已知它会减慢速度,通常是过时的统计数据。

在您的示例中,TAB_000000000019将引用索引或表(我猜测索引,因为它看起来像是自动生成的名称)。

说实话,这是一种黑色艺术,但正如我所理解的那样,这就是它的主旨。

免责声明:我不是DBA,但我已涉足该领域。

答案 1 :(得分:3)

根据您的更新:如果field1是唯一的索引字段,那么原始查询可能会对该索引进行快速全扫描(即读取索引中的每个条目并检查field1上的过滤条件),然后使用那些结果可以找到表中的行并过滤其他条件。 field1上的条件使得索引唯一扫描或范围扫描(即查找索引中的特定值或值范围)是不可能的。

可能优化器选择了这条路径,因为field1上有两个过滤谓词。优化器将计算每个的估计选择性,然后将它们相乘以确定它们的组合选择性。但在许多情况下,这将显着低估与条件匹配的行数。

NO_INDEX提示从优化器的考虑中消除了这个选项,因此它基本上符合它认为次佳的计划 - 可能在这种情况下使用基于查询中其他过滤条件的分区消除。

答案 2 :(得分:2)

与使用索引查询表相比,使用索引会导致查询性能降低更多磁盘IO。

这可以通过一个简单的表来证明:

create table tq84_ix_test (
  a number(15) primary key,
  b varchar2(20),
  c number(1)
);

以下块将100万条记录填入此表。每列250条记录在第b列填充rare value,而其他所有记录都填充frequent value

declare
  rows_inserted number := 0;
begin

  while rows_inserted < 1000000  loop

        if mod(rows_inserted, 250) = 0 then

           insert into tq84_ix_test values (
               -1 * rows_inserted, 
               'rare value',
                1);

            rows_inserted := rows_inserted + 1;

        else

           begin
              insert into tq84_ix_test values (
                 trunc(dbms_random.value(1, 1e15)),
                'frequent value',
                 trunc(dbms_random.value(0,2))
               );
               rows_inserted := rows_inserted + 1;

           exception when dup_val_on_index then 
               null;
           end;

        end if;

  end   loop;

end;
/

索引放在

列上
create index tq84_index on tq84_ix_test (b);

相同的查询,但一次使用索引,一次没有索引,性能不同。亲自检查一下:

set timing on


select /*+ no_index(tq84_ix_test) */
    sum(c)
  from 
    tq84_ix_test
  where
    b = 'frequent value';


select /*+ index(tq84_ix_test tq84_index) */
    sum(c)    
  from 
    tq84_ix_test
  where
    b = 'frequent value';

为什么?在没有索引的情况下,按顺序读取所有数据库块。通常,这是昂贵的,因此被认为是坏的。在正常情况下,使用索引,这样的“全表扫描”可以减少到读取2到5个索引数据库块,再读取包含索引指向的记录的一个数据库块。在这里的示例中,它完全不同:读取整个索引并且(几乎)索引中的每个条目,也读取数据库块。因此,不仅要读取整个表,还要读取索引。请注意,如果c也在索引中,则此行为会有所不同,因为在这种情况下,Oracle可以选择从索引中获取c的值,而不是绕道而行。

所以,概括一下这个问题:如果索引没有选择少量记录,那么不使用它可能是有益的。

答案 3 :(得分:2)

有关索引的注意事项是它们是基于行顺序和字段中数据的预先计算的值。在这种特定情况下,您说field1已编入索引,并且您在查询中使用它,如下所示:

    where   field1_ like '%someGenericString%' and 
            field1_ <> 'someSpecificString'

在上面的查询片段中,过滤器同时包含一个可变数据,因为百分比(%)字符包含字符串,然后是另一个特定字符串。这意味着不使用优化器提示的默认Oracle优化将首先尝试在索引字段中查找字符串,并且还查找数据是否是字段中数据的子字符串,然后它将检查数据与另一个特定字符串不匹配。检查索引后,将检查其他列。如果重复,这是一个非常缓慢的过程。

DBA提出的NO_INDEX提示删除了优化器使用索引的首选项,并且可能允许优化器首先选择更快的比较,而不必先强制索引比较,然后比较其他列。

以下内容很慢,因为它会比较字符串及其子字符串:

            field1_ like '%someGenericString%'

虽然以下内容更快,因为它具体:

            field1_ like 'someSpecificString'

因此,使用NO_INDEX提示的原因是,如果您对索引进行比较会减慢速度。如果将索引字段与更具体的数据进行比较,则索引比较通常更快。

我说通常是,因为当索引字段包含更多冗余数据时,如上面的@Atish中提到的那样,在返回正比较之前,它必须经过一长串比较否定值。提示产生不同的结果,因为数据库设计和表中的数据都会影响查询执行的速度。因此,为了应用提示,您需要知道您提示优化器的各个比较在数据集上是否会更快。这个过程没有捷径。在写入适当的SQL查询后应该应用提示,因为提示应该基于实际数据。

查看此提示参考:http://docs.oracle.com/cd/B19306_01/server.102/b14211/hintsref.htm

答案 4 :(得分:0)

再加上Rene和Dave所说的,这就是我在生产环境中实际观察到的:

如果索引字段上的条件返回太多匹配项,那么Oracle最好不要进行全表扫描。

我们有一个报告程序查询一个非常大的索引表 - 索引在区域代码上,而查询指定了确切的区域代码,因此Oracle CBO使用索引。

不幸的是,一个特定的区域代码占表条目的90%。

只要报告是针对其他(次要)区域代码之一运行的,它就会在不到30分钟的时间内完成,但对于主要的区域代码,它需要花费很多时间。

向SQL添加提示以强制进行全表扫描解决了问题。

希望这有帮助。

答案 5 :(得分:0)

我曾在某处读过在查询前使用%,例如'%someGenericString%'会导致Oracle忽略该字段上的INDEX。也许这就解释了为什么查询运行缓慢。