【问题标题】:Function-based Index using Substr and Instr使用 Substr 和 Instr 的基于函数的索引
【发布时间】:2014-12-07 03:01:21
【问题描述】:

我在 ORACLE 中创建了一个查询:

SELECT SUBSTR(title,1,INSTR(title,' ',1,1)) AS first_word, COUNT(*) AS word_count 
FROM FILM 
GROUP BY SUBSTR(title,1,INSTR(title,' ',1,1)) 
HAVING COUNT(*) >= 20;    

运行后的结果: 539 rows selected. Elapsed: 00:00:00.22

我需要改进它的性能并创建了一个function-based index

CREATE INDEX INDX_FIRSTWRD ON FILM(SUBSTR(title,1,INSTR(title,' ',1,1)));

在本文顶部运行相同的查询后,我仍然获得相同的性能: 539 rows selected. Elapsed: 00:00:00.22

索引没有被应用或覆盖,还是我做错了什么?

感谢您提供的任何帮助。 :)

编辑:

Execution Plan:
----------------------------------------------------------
Plan hash value: 2033354507

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      | 20000 |  2968K|   138   (2)| 00:00:02 |
|*  1 |  FILTER             |      |       |       |            |          |
|   2 |   HASH GROUP BY     |      | 20000 |  2968K|   138   (2)| 00:00:02 |
|   3 |    TABLE ACCESS FULL| FILM | 20000 |  2968K|   136   (0)| 00:00:02 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(COUNT(*)>=20)


Statistics
----------------------------------------------------------
      0  recursive calls
      0  db block gets
    471  consistent gets
      0  physical reads
      0  redo size
  14030  bytes sent via SQL*Net to client
    908  bytes received via SQL*Net from client
     37  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
    539  rows processed

【问题讨论】:

  • 查询计划是否显示正在使用索引?由于您需要读取表(或索引)中的每一行,我不认为索引会带来巨大的好处,除非 film 表比索引大很多。
  • @JustinCave 我如何显示该查询的查询计划,因为当我在查询前面键入 EXPLAIN PLAN FOR 时,我只会得到解释的结果。
  • 如果您使用的是 SQL*Plus,请在运行语句之前运行 set autotrace on。您使用的是什么版本的 Oracle?
  • @JustinCave - 我已经用执行计划编辑了我的原始帖子。我正在使用 Oracle 11g。根据执行计划,这是否意味着它没有使用索引?
  • 你抱怨响应时间是 22 毫秒???

标签: oracle performance indexing


【解决方案1】:

问题是您用于索引的值可能为 null - 如果标题中没有空格(即它是一个单词标题,如“Jaws”),那么您的 substr 评估为 null。顺便说一句,这可能不是您想要的 - 您可能希望结束位置以是否有空格为条件,但这超出了问题的范围。 (即使您更正了该逻辑,Oracle 仍可能无法相信结果不能为空,即使基础列不可为空)。 编辑:有关使用nvl 处理单字标题的更多信息,请参见下文。

由于索引中不包含空值,因此不会对单标题行进行索引。但是您要查询所有行,而 Oracle 知道索引不包含所有行,因此它不能使用索引来完成查询 - 即使您添加了一个提示告诉它,它也必须忽略该提示.

唯一使用索引的情况是,如果您包含一个也引用索引值的过滤器,并且显式或隐式排除空值,例如:

SELECT SUBSTR(title,1,INSTR(title,' ',1,1)) AS first_word, COUNT(*) AS word_count 
FROM FILM
WHERE SUBSTR(title,1,INSTR(title,' ',1,1)) IS NOT NULL
GROUP BY SUBSTR(title,1,INSTR(title,' ',1,1)) 
HAVING COUNT(*) >= 20;    

(这也可能不是您真正想要的)。

SQL Fiddle 用于带有和不带有过滤器的查询,以及带有和不带有索引提示的查询。 (单击每个结果部分的“执行计划”链接,查看它是在执行全表扫描还是全索引扫描)。

并且another Fiddle 表明如果过滤器仍然允许空值,则即使使用过滤器也不能使用索引,因为它们不在索引中。


自从 SylvainLeroux 提出以来,Oracle 还不够聪明,无法知道如果你 coalesce 它计算的值不能为空,即使基础列不为空(如 function-based index 或 @ 987654324@)。可能是因为可能有很多分支需要评估。但是,如果您改用更简单且专有的nvl,那就足够聪明了:

CREATE INDEX INDX_FIRSTWRD
ON FILM(NVL(SUBSTR(title,1,INSTR(title,' ',1,1)),title));

SELECT NVL(SUBSTR(title,1,INSTR(title,' ',1,1)),title) AS first_word,
  COUNT(*) AS word_count 
FROM FILM
GROUP BY NVL(SUBSTR(title,1,INSTR(title,' ',1,1)),title) 
HAVING COUNT(*) >= 20;    

但前提是title 被定义为非空。如果the virtual column is also declared not-null coalesce 确实有效(感谢 Sylvain)。

SQL Fiddle with a function-based indexanother with a virtual column

【讨论】:

  • 很好的解释!但是有一个问题:“即使您纠正了该逻辑,Oracle 仍可能无法相信结果不能为空” Oracle 是否足够聪明,可以理解结果可以't 使用COALESCE(SUBSTR(...), title) 时为NULL(假设标题NOT NULL)?
  • @SylvainLeroux - 好问题,我现在无法测试,但我希望如此 - 只要标题本身不为你所说的 null。我会尽量记住明天检查和更新。
  • @SylvainLeroux - apparently not。也不是with a virtual column。哈,但用nvl 代替coalesceas an FBIas a virtual column 已经足够聪明了。
  • 感谢您的回答。 Oracle 无法猜测,但当您手动指定 virtual column as being NOT NULL 时,COALESCE 可以正常工作。
【解决方案2】:

已选择 539 行。经过:00:00:00.22

您真的认为您需要调整在不到一秒内返回 539 行的查询吗? 220 毫秒,没错! 考虑一下。

在你的情况下,我认为CBO 做了最好的事情。这就是它不使用index 的原因。因为,要从表中读取every row,使用索引是一种开销。它需要读取索引然后执行table access by rowid。可能在你的小表中,它可以用更少的IO 读取整个表来获取数据。

如果表足够小,可以放在一个块中,那么它只需要一个one IO 来从single blockfull table scan 获取所需的数据。

您可以尝试通过提示查询使用索引来检查解释计划,看看是否真的有任何改进。请记住,您正在不必要地尝试提高在不到一秒内执行的查询的性能!

【讨论】:

  • 0.22 秒是 220 毫秒,而不是 22。在这种情况下,由于查询所需的所有信息都在索引中,我希望优化器会对索引进行全面扫描并忽略表(不需要table access by rowid)。不过,这可能不会比对表进行全面扫描快得多,具体取决于film 表中行的大小。
  • 是的,这是一个错字。现已更正。我同意,通过 rowid 访问表的开销会很大。 OP 没有提到该表的总行数。您对行的大小有一个很好的看法,这需要涉及多个块。我只是认为,在 OP 的情况下,使用 full table scan 从单个块中获取所需数据不超过一个 IO
猜你喜欢
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
  • 2019-04-02
  • 2018-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多