使用Substr和Instr的基于函数的索引

时间:2014-10-12 11:58:31

标签: oracle performance indexing

我在 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

2 个答案:

答案 0 :(得分:3)

问题是你用于索引的值可能为null - 如果标题中没有空格(即它是一个单字标题,如“Jaws”)那么你的substr评估为空值。顺便说一下,这可能不是你想要的 - 你可能希望最终位置取决于是否有空间,但这超出了问题的范围。 (即使你纠正了这个逻辑,Oracle仍然无法相信结果不能为null,即使基础列不可为空)。 修改:有关使用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提出了这个问题,但是如果你coalesce知道计算值不能为空,那么Oracle就不够聪明了,即使基础列不为空(作为{{3} }或function-based index)。可能是因为可能有很多分支要评估。但是如果你使用更简单和专有的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被定义为非空时。如果as a virtual column(感谢Sylvain),coalesce确实有用。

the virtual column is also declared not-nullSQL Fiddle with a function-based index

答案 1 :(得分:0)

  

选择了539行。经过时间:00:00:00.22

你真的认为你需要调整在不到一秒中返回539行的查询吗? 220毫秒,准确! 考虑一下。

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

如果表格足够小,可以放在一个块中,那么只需要one IOsingle block获取full table scan所需的数据。

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