除非我告诉它,为什么Oracle不会使用我的索引?

时间:2011-10-03 07:34:25

标签: sql oracle oracle10g

我有一个索引:

CREATE INDEX BLAH ON EMPLOYEE(SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4));

和SQL STATEMENT:

SELECT COUNT(*) 
  FROM (SELECT COUNT(*) 
          FROM EMPLOYEE 
         GROUP BY SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4) 
        HAVING COUNT(*) > 100);

但除非我添加提示,否则它会继续执行全表扫描而不是使用索引。

EMPSHIRTNO不是主键,EMPNO是(这里没有使用)。

复杂查询

EXPLAIN PLAN FOR SELECT COUNT(*) FROM (SELECT COUNT(*) FROM EMPLOYEE
                                        GROUP BY SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4)
                                       HAVING COUNT(*) > 100);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1712471557
----------------------------------------------------------------------------------
| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |          |     1 |       |    24   (9)| 00:00:01 |
|   1 |  SORT AGGREGATE       |          |     1 |       |            |          |
|   2 |   VIEW                |          |   497 |       |    24   (9)| 00:00:01 |
|*  3 |    FILTER             |          |       |       |            |          |
----------------------------------------------------------------------------------
|   4 |     HASH GROUP BY     |          |   497 |  2485 |    24   (9)| 00:00:01 |
|   5 |      TABLE ACCESS FULL| EMPLOYEE |  9998 | 49990 |    22   (0)| 00:00:01||
----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------                             
   3 - filter(COUNT(*)>100)                                                     

17 rows selected.

ANALYZE INDEX BLAH VALIDATE STRUCTURE;

SELECT BTREE_SPACE, USED_SPACE FROM INDEX_STATS;

BTREE_SPACE USED_SPACE
----------- ----------
     176032     150274

简单查询:

EXPLAIN PLAN FOR SELECT * FROM EMPLOYEE;

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
Plan hash value: 2913724801                                                     

------------------------------------------------------------------------------  
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |  
------------------------------------------------------------------------------  
|   0 | SELECT STATEMENT  |          |  9998 |   439K|    23   (5)| 00:00:01 |  
|   1 |  TABLE ACCESS FULL| EMPLOYEE |  9998 |   439K|    23   (5)| 00:00:01 |  
------------------------------------------------------------------------------  

8 rows selected.

也许是因为NOT NULL约束是通过CHECK约束强制执行的,而不是最初在表创建语句中定义的?我这样做时会使用索引:

SELECT * FROM EMPLOYEE WHERE SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4) = '1234';

对于那些暗示它无论如何都需要读取所有行的人(我认为它不会像计算那样),索引也不用于此:

SELECT SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4) FROM EMPLOYEE;

事实上,在EMPSHIRTNO上放置一个索引并从员工那里执行SELECT EMPSHIRTNO;也不使用索引。我应该指出,EMPSHIRTNO并不是唯一的,表中有重复项。

3 个答案:

答案 0 :(得分:3)

由于查询的性质,无论如何都需要扫描表格的每一行。所以oracle可能会认为全表扫描是最有效的方法。因为它使用HASH GROUP BY,所以最终没有像oracle 7天那样令人讨厌的排序。

首先得到每件SUBSTR(...)衬衫的数量。因此它的查询的第一部分必须扫描整个表

SELECT COUNT(*) 
FROM EMPLOYEE 
GROUP BY SUBSTR(TO_CHAR(EMPSHIRTNO), 1, 4)

接下来,您要放弃计数为< = 100的SUBSTR(...) .Oracle需要扫描所有行以验证这一点。从技术上讲,你可以争辩说,一旦它有101它就不再需要了,但我认为Oracle不能解决这个问题,特别是当你问它在子查询的SELECT COUNT(*)中的总数是多少时

HAVING COUNT(*) > 100);

所以基本上给你的答案是你需要Oracle扫描表中的每一行,所以索引对过滤没有帮助。因为它使用散列组,索引对分组没有任何帮助。因此,使用索引会降低查询速度,这就是Oracle不使用它的原因。

答案 1 :(得分:0)

我认为您可能需要在SUBSTR上构建基于函数的索引(TO_CHAR(EMPSHIRTNO),1,4); SQL中的函数倾向​​于使列上的常规索引无效。

答案 2 :(得分:0)

我相信@Codo是正确的。 Oracle无法确定表达式将始终为非null,然后必须假定某些空值可能不会 存储在索引中。

(似乎Oracle 应该能够发现表达式不可为空。通常,任何随机SUBSTR表达式总是存在的可能性 not null可能非常低,也许Oracle只是将所有SUBSTR表达式混为一谈?)

您可以使用以下解决方法之一使索引可用于您的查询:

--bitmap index:
create bitmap index blah on employee(substr(to_char(empshirtno), 1, 4));
--multi-column index:
alter table employee add constraint blah primary key (id, empshirtno);
--indexed virtual column:
create table employee(empshirtno varchar2(10) not null
    ,empshirtno_for_index as (substr(empshirtno,1,4)) not null );
create index blah on employee(empshirtno_for_index);