索引不能提高性能,有时会使性能变差

时间:2018-10-22 05:28:30

标签: sql oracle performance indexing oracle11g

我有一张桌子EMPLOYEE(EMP_ID, EMP_NAME, DESC, SALARY),我想找回所有可以做魔术的员工。能够执行魔术的能力被分类为一种才能,如果每个雇员都存储在每个雇员的每个DESC条目的末尾,则该才华被分类。

如果某个雇员有才能,那么该雇员的DESC将以'Talent: __talent_here__'结尾。例如,乔擅长玩杂耍,那么他的描述将类似于'Joe Doe, works here since 2011. Talent: juggling' 。我可以假设在每个描述中只有一个字符串文字'Talent: '的实例,人才总是存储在DESC的末尾,并不是所有的员工都有人才。我还想按姓名对检索到的员工进行排序,而只希望排序的前50个值

这是我目前正在使用的SELECT语句。

SELECT * FROM (
    SELECT E.EMP_NAME FROM EMP E
    WHERE INSTR(E.DESC, 'Talent: ') > 0
        AND INSTR(SUBSTR(E.DESCRIPTION, INSTR(E.DESC, 'Talent: ')), 'magic') > 0
    ORDER BY E.EMP_NAME ASC
) WHERE ROWNUM <= 50
/

SELECT语句工作正常。现在,我想在查询中创建索引以缩短运行时间:

CREATE INDEX MAGICIANS ON EMP(
    INSTR(DESCRIPTION, 'Talent: '), 
    INSTR(SUBSTR(DESCRIPTION, INSTR(DESCRIPTION, 'Talent: ')), 'magic'))
/

但是该索引似乎无效。我的表中大约有9000个条目,在创建索引之前,我的select语句的运行时间为00.50秒。创建索引后,它变为约00.49秒。我一直在尝试索引,有时创建索引后的运行时甚至比以前稍微差一点。 (从00.50到00.52秒)

有人知道为什么会这样吗?

谢谢大家。

3 个答案:

答案 0 :(得分:5)

索引不是魔术(哦,天哪!)。建立索引不能保证更快的检索时间。要了解为什么索引不能提供更快的结果,您需要了解索引的工作原理。

您的索引按照单词'Talent'的偏移量进行排列。所有没有才能的员工的零偏移,其他所有员工的零偏移。对于他们来说,偏移量是DESCRIPTION值长度的函数。至关重要的是,DESCRIPTION的长度与员工是否可以做魔术之间没有任何关系。

因此,要查找有能力的员工,数据库必须访问talent偏移量不为零的所有索引条目,然后读取magic偏移量不为零的所有索引条目,然后读取这些条目的表记录。魔术师的索引条目将分散在整个索引中,而这些记录将分散在整个表中。几乎可以肯定,进行全表扫描并从表中提取记录会更快。

另一件事是,即使您的索引适用于魔术师,对于找到魔术师也没有用。

从根本上讲,这是数据模型的失败:将TALENT嵌入DESCRIPTION会中断First Normal Form。为什么可以解决该问题-使用Virtual Columns(或建议使用实例化视图,例如@TimBiegeisen)-正确的解决方案是将TALENT建模为查找表并让EMPLOYEE引用它通过外键(从而消除了员工练习“魔术”或“马吉克”的可能性)。这样的查找表还可以让您拥有多才多艺的员工:您可以使用交集表EMPLOYEE_TALENT来建模既是魔术师又是魔术师的员工,以加入EMPLOYEETALENT表。

答案 1 :(得分:3)

索引方法的问题是B-Tree索引是围绕列中字符串的 start 建立的,而不是围绕埋在中间某个位置的子字符串周围的。为了使用索引,理想情况下,最好有一个单独的善意列,其中包含员工的才能。这里的一种方法是创建一个包含这样的人才专栏的物化视图,然后对其进行索引:

CREATE MATERIALIZED VIEW mv_name
REFRESH ON DEMAND
AS
SELECT e.*,
    CASE WHEN INSTR(DESCRIPTION, 'Talent: ') > 0
         THEN SUBSTR(DESCRIPTION, INSTR(DESCRIPTION, 'Talent: ') + 8)
     ELSE '' END AS Talent
FROM EMPLOYEE e;

CREATE INDEX idx ON mv_name (Talent);

现在,以下查询应相当快:

SELECT *
FROM mv_name
WHERE Talent = 'magic';

答案 2 :(得分:0)

APC正确。数据模型有缺陷。话虽如此,这是两个解决方案。

解决方案1 ​​。创建基于功能的索引,以提取实际人才。这样也可以搜索杂耍演员和小丑。这种方法有两个缺点。为了使Oracle使用该索引,您必须在查询中重复该表达式以完全匹配索引定义。第二个缺点是,除非再次在选择列表中重复该表达式,否则您将无法轻松提取实际才能。

create index employee_ix_talent 
    on employee(regexp_replace(descr, '^(.*)Talent: (.*)$', '\2'));

select emp_id, emp_name, salary, regexp_replace(descr, '^(.*)Talent: (.*)$', '\2') as talent
  from employee e
 where regexp_replace(descr, '^(.*)Talent: (.*)$', '\2') = 'magic';

解决方案2(首选)。将虚拟列添加到表中并对该列建立索引。这消除了第一个解决方案的两个缺点。现在,您可以选择“人才”列并对其进行搜索,最重要的是:现在,您的代码已屏蔽了表达式中的更改。

alter table employee 
  add talent generated always as(
         regexp_replace(descr, '^(.*)Talent: (.*)$', '\2')
      );

create index employee_ix_talent on employee(talent);

select emp_id, emp_name, talent, salary
  from employee e
 where talent = 'magic';