我在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决定在这个非常明确的情况下不使用索引有一个很好的解释?
答案 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;
/