Oracle 没有使用索引

Oracle is not using the Indexes

我在 oracle 11g 中有一个非常大的 table,它在 char 字段中有一个非常简单的索引(通常是 Y 或 N) 如果我只是按照下面的方式执行队列,那么 return

大约需要 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')

我得到的第二个pla:

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 使用索引,我不太适应table。

对于 oracle 决定在这种非常明确的情况下不使用索引,有人能给出一个很好的解释吗?

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

所以下一个问题当然是为什么在这种情况下它的估计值相差如此之远。不仅估计的时间相对于彼此是错误的,而且两者都比您在 运行 执行查询时实际经历的要大得多。

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

QueueProcessed 列可能缺少直方图,因此 Oracle 不知道数据是倾斜的。

如果 Oracle 不知道数据是倾斜的,它将假定相等谓词,QueueProcessed = 'N',returns DBA_TABLES.NUM_ROWS / DBA_TAB_COLUMNS.NUM_DISTINCT。优化器 认为 查询 return 是 table 中一半的行。基于80ms return 时间实际行数returned 较小。

索引范围扫描通常只在 select 一小部分行时才有效。索引范围扫描一次从数据结构中读取一个块。而如果数据是随机分布的,它可能无论如何都需要从 table 中读取每个数据块。由于这些原因,如果查询访问 table 的大部分,使用多块完整 table 扫描会更有效。

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

示例架构

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

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

第一次执行会有问题

在这种情况下,默认统计设置不会在第一次 时间收集直方图。该计划显示了完整的 table 扫描并估计行数 = 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 跟踪列何时被使用并且可以从直方图中获益,尽管如果 table 被删除,数据就会丢失。

运行 一个示例查询和重新收集统计信息将使直方图出现:

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

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

现在 Rows=100 并且使用了 Index。

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

创建直方图

尝试确定直方图丢失的原因。检查是否使用默认值收集统计信息,没有奇怪的列或 table 首选项,并且 table 没有经常删除和重新加载。

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

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