简体   繁体   English

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

[英]Why won't Oracle use my index unless I tell it to?

I have an index: 我有一个索引:

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

and an SQL STATEMENT: 和一个SQL STATEMENT:

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

but it keeps doing a full table scan instead of using the index unless I add a hint. 但除非我添加提示,否则它会一直进行全表扫描而不是使用索引。

EMPSHIRTNO is not the primary key, EMPNO is (which isn't used here). EMPSHIRTNO不是主键,EMPNO是主键(此处未使用)。

Complex query 复杂查询

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

Simple query: 简单查询:

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.

Maybe it is because the NOT NULL constraint is enforced via a CHECK constraint rather than being defined originally in the table creation statement? 可能是因为NOT NULL约束是通过CHECK约束而不是最初在表创建语句中定义的? It will use the index when I do: 这样做时,它将使用索引:

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

For those suggesting that it needs to read all of the rows anyway (which I don't think it does as it is counting), the index is not used on this either: 对于那些建议它仍然需要读取所有行的人(我不认为它正在计数),也没有在该索引上使用索引:

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

In fact, putting an index on EMPSHIRTNO and performing SELECT EMPSHIRTNO FROM EMPLOYEE; 实际上,在EMPSHIRTNO上建立索引,然后从EMPLOYEE执行SELECT EMPSHIRTNO; does not use the index either. 也不使用索引。 I should point out that EMPSHIRTNO is not unique, there are duplicates in the table. 我应该指出,EMPSHIRTNO不是唯一的,表中有重复项。

Because of the nature of your query it needs to scan every row of the table anyway. 由于查询的性质,它无论如何都需要扫描表的每一行。 So oracle is probably deciding that a full table scan is the most efficient way to do this. 因此,oracle可能会决定全表扫描是执行此操作的最有效方法。 Because its using a HASH GROUP BY there is no nasty sort at the end like in oracle 7 days. 因为它使用HASH GROUP BY ,所以最后没有像oracle 7天那样令人讨厌的排序。

First get the count per SUBSTR(...) of shirt no. 首先获取衬衫编号SUBSTR(...)的计数。 Its thus first part of the query which has to scan the entire table 因此,它是查询的第一部分,必须扫描整个表

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

Next you want to discard the SUBSTR(...) where the count is <= 100. Oracle needs to scan all rows to verify this. 接下来,您要丢弃计数小于等于100的SUBSTR(...) 。Oracle需要扫描所有行以进行验证。 Technically you could argue that once it has 101 it doesn't need any more, but I don't think Oracle can work this out, especially as you are asking it what the total numer is in the SELECT COUNT(*) of the subquery. 从技术上讲,您可能会争辩说,一旦它有了101,就不再需要了,但是我认为Oracle不能解决这个问题,尤其是当您询问它子查询的SELECT COUNT(*)中的总数字是多少时。

HAVING COUNT(*) > 100);

So basically to give you the answer you want Oracle needs to scan every row in the table, so an index is no help on filtering. 因此,基本上可以给您想要的答案,Oracle需要扫描表中的每一行,因此索引对筛选没有帮助。 Because its using a hash group by, the index is no help on the grouping either. 由于其使用哈希分组依据,因此索引对分组也无济于事。 So to use the index would just slow your query down, which is why Oracle is not using it. 因此,使用索引只会降低查询速度,这就是Oracle不使用索引的原因。

I think you may need to build a function-based index on SUBSTR(TO_CHAR(EMPSHIRTNO), 1,4); 我认为您可能需要在SUBSTR(TO_CHAR(EMPSHIRTNO),1,4)上构建基于函数的索引; Functions in your SQL have a tendency to invalidate regular indexes on a column. SQL中的函数倾向​​于使列上的常规索引无效。

I believe @Codo is correct. 我相信@Codo是正确的。 Oracle cannot determine that the expression will always be non-null, and then must assume that some nulls may not be stored in the index. Oracle无法确定表达式将始终为非null,然后必须假定某些null可能未存储在索引中。

(It seems like Oracle should be able to figure out that the expression is not nullable. In general, the chance of any random SUBSTR expression always being not null is probably very low, maybe Oracle just lumps all SUBSTR expressions together?) (看来Oracle 应该能够弄清楚该表达式不可为空。通常,任何随机SUBSTR表达式始终不为null的可能性可能很小,也许Oracle会将所有SUBSTR表达式都混在一起了?)

You can make the index usable for your query with one of these work-arounds: 您可以使用以下解决方法之一使索引可用于您的查询:

--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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM