【问题标题】:Why won't Oracle use my index unless I tell it to?除非我告诉它,否则 Oracle 为什么不使用我的索引?
【发布时间】:2011-10-03 07:34:25
【问题描述】:

我有一个索引:

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

和一个 SQL 语句:

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 FROM EMPLOYEE;也不使用索引。我应该指出,EMPSHIRTNO 不是唯一的,表中有重复。

【问题讨论】:

  • 这不是一个有效的查询。它的左括号多于右括号。
  • 请显示两个查询计划(有和没有提示)。您最近是否更新了数据库统计信息?
  • @Codo 出于某种原因,我什至无法让它使用带有提示的索引。我已经更新了统计数据。我认为问题在于查询不使用 where SUBSTR.... 子句,因此数据库不想使用索引。
  • 也许甲骨文比你聪明? (开个玩笑),但 FTS 并不总是一件坏事。在这种情况下,做 FTS 可能会更快/更好
  • Oracle 很可能无法确定函数 SUBSTR(TO_CHAR(xx), 1, 4) 的结果是否为 NOT NULL。所以它假设它是可空的,因此索引不包含所有行。所以它不能也不会使用它。

标签: sql oracle oracle10g


【解决方案1】:

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

首先计算每个SUBSTR(...) 的衬衫号码。因此,它是查询的第一部分,必须扫描整个表

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

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

HAVING COUNT(*) > 100);

所以基本上给你你想要的答案Oracle需要扫描表中的每一行,所以索引对过滤没有帮助。因为它使用哈希分组,所以索引对分组也没有帮助。所以使用索引只会减慢您的查询速度,这就是 Oracle 不使用它的原因。

【讨论】:

  • +1,复杂查询正在执行查询所需的最少操作。
【解决方案2】:

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

【讨论】:

  • 表已经有基于函数的索引。请参阅第二行问题。
【解决方案3】:

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

(似乎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);

【讨论】:

    猜你喜欢
    • 2021-09-04
    • 1970-01-01
    • 2014-06-09
    • 2019-03-09
    • 2015-10-11
    • 2022-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多