Oracle SQL中的索引,EXPLAIN PLAN和记录访问

时间:2011-11-17 01:35:25

标签: sql oracle database-design

我一直在学习Oracle SQL中的索引,我想用测试表进行一个小实验,看看索引是如何工作的。正如我在此前发表的一篇文章中所发现的,最好的方法是使用EXPLAIN PLAN。但是,我遇到了令我困惑的事情。

我的示例表包含属性(EmpID,Fname,Lname,Occupation,....等)。我使用我编写的java程序(随机名称,职业等)用500,000条记录填充它。现在,这里有一些带索引和不带索引的示例查询:

没有索引:

SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

EXPLAIN PLAN说:

OPERATION                         OPTIMIZER COST
TABLE ACCESS(FULL) TEST.EMPLOYEE  ANALYZED  1169

现在我创建索引:

CREATE INDEX occupation_idx
    ON EMPLOYEE (Occupation);

WITH INDEX“occupation_idx”:

SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

EXPLAIN PLAN说:

OPERATION                         OPTIMIZER COST
TABLE ACCESS(FULL) TEST.EMPLOYEE  ANALYZED  1169

所以...成本仍然相同,1169?现在我试试这个:

WITH INDEX“occupation_idx”:

SELECT Occupation FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

EXPLAIN PLAN说:

OPERATION                              OPTIMIZER COST
INDEX(RANGE SCAN) TEST.OCCUPATION_IDX  ANALYZED  67

因此,似乎仅当该列是我从中提取值的列时才使用索引。但我认为索引的重点是使用索引列作为关键来解锁整个记录?上面的搜索是一个非常毫无意义的搜索...它会搜索您已经知道的值。唯一有价值的查询我可以想到哪个只涉及索引列的值(而不是记录的其余部分)将是一个聚合,如COUNT或其他。

我错过了什么?

4 个答案:

答案 0 :(得分:5)

即使使用索引,Oracle也决定对第二个查询进行全面扫描。

为什么这样做? Oracle会创建两个计划并为每个计划提出成本: -

1)全扫描

2)索引访问

Oracle以较低的成本选择了该计划。显然,它提出全扫描成本较低。

如果您想查看索引计划的成本,可以使用这样的提示来执行解释计划以强制使用索引:

SELECT /*+ INDEX(EMPLOYEE occupation_idx) */ Fname
FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

如果您对上述内容做了解释计划,您会发现费用高于全部扫描费用。这就是Oracle不选择使用索引的原因。

考虑指数计划成本的一种简单方法是: -

  • 索引的瑕疵(必须从上到下读取多少个块)
  • 对于索引中匹配的记录,必须随后读取的表块数。这取决于甲骨文对拥有“医生”职业的员工人数的估计。在您的简单示例中,这将是:

    行数/不同值的数量

更复杂的考虑因素包括聚类工厂和索引成本调整,它们都反映了读取的块已经存储在内存中的可能性,因此不需要从磁盘读取。

也许您可以使用索引提示的查询结果以及此查询的结果更新您的问题: -

SELECT COUNT(*), COUNT(DISTINCT( Occupation ))
FROM EMPLOYEE;

这将允许人们评论指数计划的成本。

答案 1 :(得分:3)

我想我知道这里发生了什么。

当你有索引时,你会这样做:

SELECT Occupation FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

执行计划将使用索引。这是一个明智的选择,因为满足查询所需的所有数据都在索引中,Oracle根本不需要引用该表。

但是,当你这样做时:

SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

然后,如果Oracle使用索引,它将执行INDEX RANGE SCAN,然后执行TABLE ACCESS BY ROWID以查找与该Occupation对应的Fname。现在,根据有多少行拥有DOCTOR for Occupation,Oracle将不得不进行一次或多次访问该表,以查找Fname。例如,如果您有一个表,并且所有员工都将Occupation设置为'DOCTOR',则该索引没有多大用处,Oracle将只执行该表的FULL TABLE SCAN。如果有10,000名员工,而且只有一名是医生,那么再次,这是一个明智的做法,Oracle将使用该索引。

但是当你处于这两个极端之间时,有一些微妙之处。在讨论是否使用索引时,人们喜欢谈论“选择性”,即索引标识了多少行,而不是表的大小。但是,那不是真的是真的。 Oracle 真正关心的是块选择性。也就是说,为了满足查询需要访问多少?那么,首先,RANGE SCAN的“宽”程度如何?谓词值指定的值范围越有限,越好。其次,当您的查询需要进行表查找时,必须访问多少个不同的块才能找到所需的所有数据。也就是说,中的数据相对于索引顺序的“随机”程度如何?这称为CLUSTERING_FACTOR。如果您分析索引以收集统计信息,然后查看USER_INDEXES,您将看到现在已填充CLUSTERING_FACTOR。

那么,什么是CLUSTERING_FACTOR? CLUSTERING_FACTOR是表的“有序性”,与索引的键列有关。 CLUSTERING_FACTOR的值始终介于表中的块数和表中的行数之间。 CLUSTERING_FACTOR,即非常接近表中数量的那个,表示相对于索引非常有序的表。 CLUSTERING_FACTOR,即非常接近表中的数量,相对于索引而言非常无序。

理解CLUSTERING_FACTOR描述中相对于索引的数据顺序是一个重要的概念。因此,例如,重建索引将更改CLUSTERING_FACTOR。同样重要的是要理解同一个表可以有两个索引,一个可以有一个优秀的CLUSTERING_FACTOR,另一个可能有一个极差的CLUSTERING_FACTOR。表本身只能以一种方式订购。

那么,为什么我花了这么多时间来描述CLUSTERING_FACTOR?因为当你有一个执行计划执行INDEX RANGE SCAN后跟TABLE ACCESS BY ROWID时,你可以确定Oracle的优化器已经考虑了CLUSTERING_FACTOR,以提出执行计划。例如,假设您有10,000行表,并假设其中100行具有Occupation ='DOCTOR'。您编写上面的查询,询问职业为DOCTOR的员工的Fname。好吧,Oracle可以非常轻松有效地确定占用是DOCTOR的行的rowid。但是,Oracle需要访问多少个 table 块才能进行Fname查找?如果数据按表中的Occupation进行聚类(排序),则它可能只有1或2个表块。但是,如果表中的数据非常无序,它可能会多达100个!因此,再次,10,000行表,并且,假设,(为了说明和简单的数学的目的)该表具有100行/块,因此,100块。根据表顺序(即CLUSTERING_FACTOR),表块访问次数可以少至1次,也可以多达100次。

所以,我希望这可以帮助您理解为什么优化器在某些情况下可能不愿意使用索引。

答案 2 :(得分:2)

索引是表的副本,它只存储以下数据:

  • 索引字段
  • 指向原始行(rowid)的指针。

假设你有一张这样的表:

rowid    id  name  occupation
[1]      1   John  clerk
[2]      2   Jim   manager
[3]      3   Jane  boss

然后occupation上的索引如下所示:

occupation  rowid
boss        [3]
manager     [2]
clerk       [1]

,记录按occupation中的B-Tree排序。

如您所见,如果您只选择索引字段,则只需要索引(第二个表)。

如果您选择occupation以外的任何内容:

SELECT  *
FROM    mytable
WHERE   occupation = 'clerk'

然后引擎应该做两件事:首先在索引中找到相关记录,第二,通过rowid查找原始表中的记录。就像你加入rowid上的两个表格一样。

由于索引中的rowid不按顺序排列,因此对原始表的读取不是顺序的,而且可能很慢。按顺序读取原始表格可能会更快,只需使用occupation = 'clerk'过滤记录。

引擎没有“解锁”记录:它只是在索引中找到rowid,如果索引本身没有足够的数据,它会在找到rowid的原始表中查找数据

答案 3 :(得分:0)

作为WAG。分析表和索引,然后查看计划是否更改。

当您只选择职业时,可以从索引中满足整个查询。该指数字面上有一份职业。在向select中添加其他列的那一刻,Oracle必须转到数据记录才能获得它。优化器选择读取所有数据行而不是所有索引行和数据行。它更便宜。