为啥 oracle 表索引但仍然进行全表扫描?

Posted

技术标签:

【中文标题】为啥 oracle 表索引但仍然进行全表扫描?【英文标题】:Why oracle table indexed but still do full table scan?为什么 oracle 表索引但仍然进行全表扫描? 【发布时间】:2013-01-07 05:08:01 【问题描述】:

我有一个包含 3000K 行的表“MSATTRIBUTE”。我使用以下查询来检索数据,该查询具有不同的执行计划,具有相同的数据库数据但在不同的环境中。在一个环境中,它显示为全表扫描,因此查询非常慢,但在另一个环境中,它都使用索引扫描,这非常好,每个人都知道为什么它在一个环境中进行全表扫描,因为我为他们建立了索引,我该怎么做让我们成为索引扫描,就像我在 env 1 中测试的那样。我如何改进这个查询?

【问题讨论】:

不是3000K == 3M 吗? ;) 最近是否添加了索引(我不清楚),如果是,您从那时起是否收集了统计数据?您可以查看all_tablesall_indexeslast_analyzed列;对于新索引,后者可能为空。如果是这种情况,优化器可能不会考虑使用索引。 索引是否存在于所有环境中?您是否在每个环境中使用相同的软件版本?你的环境是什么? 索引是建表的时候建的,所以最近没有加。数据是一样的,我使用 imp/exp 从一个数据库(env)导入并导出到另一个 env,唯一的区别是一个 env 是使用全表扫描的 oracle 11g R1,另一个使用索引扫描的 db 是 11g R2,但我认为这个小的版本差异不会影响执行计划。 @Kur.C 另一方面,R1 和 R2 之间可能存在代码差异,这完全解释了执行计划的差异。 【参考方案1】:

如果两个表中的行顺序不同,则两个系统中的索引可能具有不同的聚类因子,因此估计索引访问的成本不同。检查表和索引统计信息,包括聚类因子,看看是否有显着差异。

另外,这两个系统的解释计划中是否提到了动态抽样?

【讨论】:

我使用 imp/exp 从一个数据库(env)导入并导出到另一个 env,因此该行具有相同的顺序。检查了两个环境,两个索引状态都有效。动态SQL也一样,都是全表扫描。 你可以看到 where 子句有 4 个条件,其中两列被索引,但是两个:CHANGE_RELEASE_DATE(date type) 和 class(number) 没有被索引,这会导致一个 env 使用完整表扫描? @Kur.C: exp/imp 不保留行顺序。 @Kur.C 检查索引“统计”,而不是“状态”。如果聚类因子不同,则行的顺序可能不同,但这也取决于两个版本的表使用了多少块。 谢谢,我会检查索引“统计”,看看有什么不同【参考方案2】:

如果不了解您的数据模型和您的业务,就很难给出具体的积极建议。但这里有一些关于您的索引策略的注释,以及为什么我猜优化器没有使用您拥有的索引。

在子查询中,从三列到 REDLINE_MSATTRIBUTE 驱动器的访问路径:

类 OBJECT_ID CHANGE_RELEASE_DATE。

CLASS 未编入索引。但这大概不是很有选择性。 OBJECT_ID 是复合索引的前导列,但其他列与子查询无关。

但最大的问题是 CHANGE_RELEASE_DATE。这根本没有索引。这是个坏消息,因为您的一个主键查找会产生一个日期,然后将其与 CHANGE_RELEASE_DATE 进行比较。如果一列没有被索引,那么数据库必须读取表来获取它的值。

主查询结束

ATTID CHANGE_ID OBJECT_ID(再次) CHANGE_RELEASE_DATE(再次) CLASS(再次) OLD_VALUE

ATTID 已编入索引,但该索引的选择性如何?优化器可能不认为它很有选择性。 ATTID 也在一个带有 CHANGE_ID 和 OLD_VALUE 的复合索引中,但它们都不是前导列,所以这不是很有用。我们已经讨论过 CLASS、CHANGE_RELEASE_DATE 和 OBJECT_ID。

如果索引比表扫描便宜(读取次数更少),优化器只会选择使用索引。这通常意味着 WHERE 子句条件需要映射到索引的 前导(即最左边)列。子查询中的 OBJECT_ID 和 ATTID 可能就是这种情况,除了

    执行计划必须执行 INDEX SKIP SCAN,因为 REDLINE_MSATTRIBUTE_INDEX1 在两列之间有 CHANGE_ID 无论如何,数据库都必须转到表才能获取 CLASS 和 CHANGE_RELEASE_DATE。

因此,您可以通过在(CHANGE_RELEASE_DATE, CLASS, OBJECT_ID, ATTID) 上建立索引来获得一些改进。但正如我前面所说,在不了解您的情况的情况下,这些只是不明智的猜测。

【讨论】:

非常感谢您的详细信息,是的,OBJECT_ID 将最大限度地减少数据,然后保持列具有几乎相同的选择性。 OBJECT_ID 具有唯一索引会更好吗?即使这不是一个好策略,但并不慢到几分钟,但是,在一个 env 中,它使用了全表扫描,浪费了几分钟来执行,你知道为什么两个 env 有不同的执行计划吗?跨度> 【参考方案3】:

一个直接的问题是这块 SELECT RELEASE_DATE FROM CHANGE WHERE ID = 136972355(这段代码将为返回的每一行运行,它不需要......更好的方法是使用单个笛卡尔表,所以它只运行一次并返回一个静态值进行比较....

示例 1:

Select * From Table1, (Select Sysdate As Compare From Dual) Table2 Where Table1.Date > Table2.Compare.

总是比 从表 1 中选择 * 其中 Date > Sysdate -- Sysdate 将为每一行调用,因为它是基于动态函数的值。前面的示例将解析一次文字并大大加快速度。我相信这绝对是一次伤害您的查询并强制进行表扫描的部分。

我也相信这是执行查询的更有效方式。

Select  
            REDLINE_MSATTRIBUTE.ATTID
           ,REDLINE_MSATTRIBUTE.VALUE
  From 
            REDLINE_MSATTRIBUTE
           ,(
                SELECT  ATTID
                        ,CHANGE_ID
                        ,MIN(CHANGE_RELEASE_DATE) RELEASE_DATE
                 FROM   REDLINE_MSATTRIBUTE
                       ,(SELECT RELEASE_DATE FROM CHANGE WHERE ID = 136972355) T_COMPARE                
                WHERE   CLASS             = 9000
                  And   OBJECT_ID           = 32718015
                  And   CHANGE_RELEASE_DATE > T_COMPARE.RELEASE_DATE
                  And   ATTID IN (1564, 1565)
                GROUP 
                   BY   ATTID,
                        CHANGE_ID
            ) T_DYNAMIC
Where         
        REDLINE_MSATTRIBUTE.ATTID = T_DYNAMIC.ATTID   
    And REDLINE_MSATTRIBUTE.CHANGE_ID = T_DYNAMIC.CHANGE_ID
    And REDLINE_MSATTRIBUTE.RELEASE_DATE = T_DYNAMIC.RELEASE_DATE
    And CLASS     = 9000
    And OBJECT_ID = 32718015
    And OLD_VALUE ='Y'
Order 
   By   REDLINE_MSATTRIBUTE.ATTID,
        REDLINE_MSATTRIBUTE.VALUE;  

【讨论】:

我不确定您的断言(这段代码将针对返回的每一行运行)。查看执行计划。 @Florian +1,因为这些结果集即使相互关联也可以被缓存。 当查询运行时,它必须检查发布日期的当前状态,因为它可能在您的查询过程中发生变化......取决于您的隔离级别,这可能会缓存或不缓存.. . 基数方法保证运行一次以获得静态值并解析为文字并且总是更快....如果您在百万行表上测试 Where date > sysdate ,笛卡尔方法将快 10% 到 30%... 如果您将隔离级别设置为已提交读取,则会有额外的开销来确保数据库引擎不断检查发布日期以查看它是否具有新的已提交值......因此更多开销......笛卡尔积方法保证评估一次静态值......这也是 asktom 上讨论的技巧...... @Tim Oracle 无需重新检查值是否已更改。 Oracle 使用 UNDO 处理语句级一致性的方式,始终保证查询的所有部分从同一时间点返回数据。 Oracle 没有未提交的读取,因此您总是要为这些开销付费。 Subquery caching 应该阻止该子查询为每一行运行,尽管我不确定它引入了哪个版本。【参考方案4】:

当 oracle 有索引并决定使用/不使用它时,可能是因为 1)您可能对 OPTIMIZER_MODE 有不同的设置 - 确保它不在 RBO 上。 2) 数据不同 - 在这种情况下,oracle 可能会以不同方式评估查询统计信息。 3)数据相同,但统计数据不是最新的。在这种情况下 - 收集统计数据

dbms_stats.gather_table_stats('your3000Ktable',cascade=>true);

4) oracle 不会在一个环境中使用索引的原因还有很多,我建议比较参数(例如 OPTIMIZER_INDEX_COST_ADJ 等...)

【讨论】:

以上是关于为啥 oracle 表索引但仍然进行全表扫描?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的查询仍然在 Redshift 中使用 sortkey 进行全表扫描?

在oracle里,一个sql多表查询,单独执行能利用索引提高速率,但外层套上分页时,会全表扫描,如何解决,求助高手

mysql select * order by 索引 limit0,10 为啥是全表扫描

警惕 Oracle 索引优化时陷阱之无效的索引范围扫描(INDEX RANGE SCAN)导致的全表扫描

Oracle SQL优化必要的全表扫描思路分析

登录/全表扫描的低效 SQL (UserStore.FindByNameAsync)