【问题标题】:Indexes, EXPLAIN PLAN, and record access in Oracle SQLOracle SQL 中的索引、EXPLAIN PLAN 和记录访问
【发布时间】:2011-12-30 22:56:23
【问题描述】:

我一直在学习 Oracle SQL 中的索引,我想用一个测试表做一个小实验,看看索引是如何工作的。正如我从之前的一篇文章中发现的那样,最好的方法是使用 EXPLAIN PLAN。但是,我遇到了一些让我感到困惑的事情。

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

没有索引:

SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

解释计划说:

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

现在我创建索引:

CREATE INDEX occupation_idx
    ON EMPLOYEE (Occupation);

带有索引“occupation_idx”:

SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

解释计划说:

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

所以...成本还是一样,1169?现在我试试这个:

带有索引“occupation_idx”:

SELECT Occupation FROM EMPLOYEE WHERE Occupation = 'DOCTOR';

解释计划说:

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

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

我错过了什么?

【问题讨论】:

  • 有趣的问题,我很想知道为什么会这样。
  • 是否为EMPLOYEE 表定义了主键?
  • 索引可能没有完成构建吗?如果您重新运行查询 SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';它的成本下降了吗?
  • @OMGPonies:不,其实没有PK,但是我加了一个,然后drop掉重新创建索引,结果还是一样。
  • @xQbert:不,这不会改变任何事情。不过还是谢谢。

标签: sql oracle database-design


【解决方案1】:

即使有了您的索引,Oracle 还是决定对第二个查询进行全面扫描。

为什么要这样做? Oracle 会制定两个计划并为每个计划制定成本:-

1) 全面扫描

2) 索引访问

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

如果你想查看索引计划的成本,你可以做一个解释计划,带有这样的提示来强制索引使用:

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

如果你在上面做一个解释计划,你会发现成本大于全扫描成本。这也是 Oracle 没有选择使用索引的原因。

考虑索引计划成本的简单方法是:-

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

    行数/不同值的数量

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

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

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

这将允许人们评论索引计划的成本。

【讨论】:

  • 感谢您的回复。我从来不知道“提示”。但是,它似乎不起作用。即使有这个提示,它仍然会进行全面扫描,就像没有提示一样。
  • 感谢更正提示。当我创建我的表时,我故意让我的随机化脚本对每个字段都有不同的大小域。例如,lname 字段从 500 个 lname 中进行选择,而 fname 字段仅从 100 个中进行选择。我知道最终分析这些变化会很有趣。使用提示,我现在能够准确指出索引值得使用的大小域(顺便说一下,当域可能有大约 450 个值时......或者换一种说法:当索引找到的行占不到表格的 0.2%)。
  • 这是关于不同职业的数量,而不是 fnames。因为你选择使用职业。
  • 正确的弗洛林,这就是我发现的。我在每一列上都创建了索引,对于具有许多不同值的列的搜索,索引搜索的成本降低了更多。
【解决方案2】:

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

当您只选择职业时,可以从索引中满足整个查询。索引字面上有一份职业的副本。当您向选择中添加额外的列时,Oracle 必须转到数据记录以获取它。优化器选择读取所有数据行而不是所有索引行和数据行。它更便宜。

【讨论】:

  • 那么,全表扫描是更便宜的方法。我们在这里谈论什么样的数据量?
  • 在你的箭袋上添加另一个箭头。学习将 AUTOTRACE 与 SQLPLUS 一起使用。 adp-gmbh.ch/ora/sqlplus/autotrace.html
  • 表中有 500,000 条记录。共有10个属性。我只是不明白为什么它只在搜索该属性时使用索引,但如果我搜索任何其他属性(仍然使用索引属性作为搜索条件)则不使用。
  • 在我的两个测试中,where 条件仅命中索引属性。在一项测试中,select 子句命中了一个未索引的属性(并且未使用索引)。在另一个测试中,select 子句只命中索引属性(并且使用了索引)。我相信在这两种情况下都应该使用索引,因为我认为它使用索引列值作为键来解锁整个记录。
【解决方案3】:

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

  • 索引字段
  • 指向原始行的指针 (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]

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

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

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

SELECT  *
FROM    mytable
WHERE   occupation = 'clerk'

那么引擎要做两件事:一是在索引中找到相关记录,二是通过rowid找到原表中的记录。就像你在rowid 上加入了两个表一样。

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

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

【讨论】:

    【解决方案4】:

    我想我明白这里发生了什么。

    当你有索引并且你这样做时:

    SELECT Occupation FROM EMPLOYEE WHERE Occupation = 'DOCTOR';
    

    执行计划将使用索引。这很简单,因为满足查询所需的所有数据都在索引中,而 Oracle 甚至根本不需要引用该表。

    但是,当你这样做时:

    SELECT Fname FROM EMPLOYEE WHERE Occupation = 'DOCTOR';
    

    然后,如果 Oracle 使用索引,它将执行 INDEX RANGE SCAN,然后执行 TABLE ACCESS BY ROWID 来查找与该职业对应的 Fname。现在,根据 DOCTOR for Occupation 的行数,Oracle 将不得不对表进行一次或多次访问,以查找 Fname。例如,如果您有一张表,并且所有员工的 Occupation 都设置为“DOCTOR”,则索引没有多大用处,Oracle 将简单地对该表进行 FULL TABLE SCAN。如果有 10,000 名员工,而只有一名是 DOCTOR,那么再一次,这很简单,Oracle 将使用该索引。

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

    那么,什么是 CLUSTERING_FACTOR? CLUSTERING_FACTOR 是表的“有序性”,相对于索引的键列。 CLUSTERING_FACTOR 的值总是介于表中的块数和表中的行数之间。 low CLUSTERING_FACTOR,即非常接近表中blocks 数量的CLUSTERING_FACTOR,表示相对于索引非常有序的表。 highCLUSTERING_FACTOR,即非常接近表中rows数的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。

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

    【讨论】:

      猜你喜欢
      • 2018-04-30
      • 2012-08-13
      • 1970-01-01
      • 2012-05-04
      • 2013-01-01
      • 2018-12-31
      • 1970-01-01
      • 1970-01-01
      • 2013-07-13
      相关资源
      最近更新 更多