Oracle没有使用索引

时间:2015-05-28 00:47:14

标签: oracle indexing oracle11g sql-execution-plan

我在oracle 11g中有一个非常大的表,它在char字段中有一个非常简单的索引(通常是Y或N) 如果我只是执行队列,则返回

需要大约10秒
select QueueId, QueueSiteId, QueueData from queue where QueueProcessed = 'N'

但是,如果我强制它使用我创建的索引需要80毫秒

select /*+ INDEX(avaqueue QUEUEPROCESSED_IDX) */ QueueId, QueueSiteId, QueueData  
  from queue where QueueProcessed = 'N'

如果我按照下面的解释计划运行:

explain plan for select QueueId, QueueSiteId, QueueData 
  from queue where QueueProcessed = 'N'

explain plan for select /*+ INDEX(avaqueue QUEUEPROCESSED_IDX) */ 
  QueueId, QueueSiteId, QueueData 
  from queue where QueueProcessed = 'N'

对于我得到的第一个计划:

------------------------------------------------------------------------------

Plan hash value: 803924726

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   691K|   128M| 12643   (1)| 00:02:32 |
|*  1 |  TABLE ACCESS FULL| AVAQUEUE |   691K|   128M| 12643   (1)| 00:02:32 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("QUEUEPROCESSED"='N')

对于我得到的第二个人:

Plan hash value: 2012309891

--------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                    |   691K|   128M| 24386   (1)| 00:04:53 |
|   1 |  TABLE ACCESS BY INDEX ROWID| AVAQUEUE           |   691K|   128M| 24386   (1)| 00:04:53 |
|*  2 |   INDEX RANGE SCAN          | QUEUEPROCESSED_IDX |   691K|       |  1297   (1)| 00:00:16 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("QUEUEPROCESSED"='N')

------------------------------------------------------------------------------

什么证明如果我没有明确告诉oracle使用索引它不使用它,我的问题是为什么oracle不使用这个索引? Oracle通常足够聪明,可以做出比我好10倍的决策,这是我第一次强迫oracle使用索引而且我对它不太满意。

有没有人对oracle决定在这个非常明确的情况下不使用索引有一个很好的解释?

2 个答案:

答案 0 :(得分:2)

答案 - 至少是第一个只会导致更多问题的答案 - 就在计划中。第一个计划的估计成本和估计执行时间约为第二个计划的一半。在没有提示的情况下,Oracle正在选择它认为运行得更快的计划。

当然,下一个问题是为什么在这种情况下它的估计到目前为止。不仅相对于彼此的估计时间错误,两者都远大于您在运行查询时实际遇到的时间。

我要看的第一件事是估计返回的行数。在两种情况下,优化器都在猜测表中有大约691,000行与谓词匹配。这接近事实,还是很遥远?如果它很遥远,那么刷新统计数据可能是正确的解决方案。虽然如果该列只有两个可能的值,但如果现有的统计数据偏离基础,我会感到有些惊讶。

答案 1 :(得分:2)

QueueProcessed列可能缺少直方图,因此Oracle不知道数据是否有偏差。

如果Oracle不知道数据是否有偏差,它将假定等式谓词QueueProcessed = 'N'返回DBA_TABLES.NUM_ROWS /  DBA_TAB_COLUMNS.NUM_DISTINCT。优化器认为查询返回表中一半的行。根据80ms的返回时间,返回的实际行数很小。

索引范围扫描通常仅在选择一小部分行时才能正常工作。索引范围扫描一次从一个数据结构读取数据结构。如果数据是随机分布的,则可能需要从表中读取每个数据块。出于这些原因,如果查询访问表的大部分,则使用多块全表扫描更有效。

来自偏斜数据的不良基数估计导致Oracle认为全表扫描更好。创建直方图将解决问题。

示例架构

创建一个表,用倾斜的数据填充它,并在第一次收集统计信息。

drop table queue;

create table queue(
    queueid number,
    queuesiteid number,
    queuedata varchar2(4000),
    queueprocessed varchar2(1)
);
create index QUEUEPROCESSED_IDX on queue(queueprocessed);

--Skewed data - only 100 of the 100000 rows are set to N.
insert into queue
select level, level, level, decode(mod(level, 1000), 0, 'N', 'Y')
from dual connect by level <= 100000;

begin
    dbms_stats.gather_table_stats(user, 'QUEUE');
end;
/

首次执行会出现问题。

在这种情况下,默认统计信息设置不会收集第一个时间的直方图。该计划显示全表扫描并估计行数= 50000,恰好是一半。

explain plan for
select QueueId, QueueSiteId, QueueData 
from queue where QueueProcessed = 'N';

select * from table(dbms_xplan.display);

Plan hash value: 1157425618

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       | 50000 |   878K|   103   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| QUEUE | 50000 |   878K|   103   (1)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("QUEUEPROCESSED"='N')

创建直方图

默认统计信息设置通常就足够了。出于若干原因可能无法收集直方图。可以手动禁用它们 - 检查DBA设置的任务,作业或首选项。

此外,直方图仅自动收集在倾斜和使用的列上。收集直方图可能需要一些时间,不需要在从不在相关谓词中使用的列上创建直方图。 Oracle会跟踪何时使用列,并且可以从直方图中受益,但如果表被删除则数据会丢失。

运行示例查询并重新收集统计信息将显示直方图:

select QueueId, QueueSiteId, QueueData 
from queue where QueueProcessed = 'N';

begin
    dbms_stats.gather_table_stats(user, 'QUEUE');
end;
/

现在Rows = 100并且使用了索引。

explain plan for
select QueueId, QueueSiteId, QueueData 
from queue where QueueProcessed = 'N';

select * from table(dbms_xplan.display);

Plan hash value: 2630796144

----------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                    |   100 |  1800 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| QUEUE              |   100 |  1800 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | QUEUEPROCESSED_IDX |   100 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("QUEUEPROCESSED"='N')

这是直方图:

select column_name, histogram
from dba_tab_columns
where table_name = 'QUEUE'
order by column_name;

COLUMN_NAME      HISTOGRAM
-----------      ---------
QUEUEDATA        NONE
QUEUEID          NONE
QUEUEPROCESSED   FREQUENCY
QUEUESITEID      NONE

创建直方图

尝试确定缺少直方图的原因。检查是否使用默认值收集统计信息,没有奇怪的列或表首选项,并且不会不断删除和重新加载该表。

如果您不能依赖于流程的默认统计作业,您可以使用method_opt参数手动收集直方图,如下所示:

begin
    dbms_stats.gather_table_stats(user, 'QUEUE', method_opt=>'for columns size 254 queueprocessed');
end;
/