2012-09-24 66 views
24

我有一个表MYTABLE,它的日期列为SDATE,它是表的主键,并且具有唯一的索引。从表中选择最小值和最大值比预期的要慢

当我运行此查询:

SELECT MIN(SDATE) FROM MYTABLE 

它给回答瞬间。同样的情况适用于:

SELECT MAX(SDATE) FROM MYTABLE 

但是,如果我查询两者一起:

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE 

它需要更多的时间来执行。我分析了这些计划,发现在查询最小或最大值时,它使用INDEX FULL SCAN(MIN/MAX),但是当两者同时被查询时,它会进行FULL TABLE SCAN。

为什么?

测试数据:

版本11g

create table MYTABLE 
(
    SDATE DATE not null, 
    CELL VARCHAR2(10), 
    data NUMBER 
) 
tablespace CHIPS 
    pctfree 10 
    pctused 40 
    initrans 1 
    maxtrans 255 
    storage 
    (
    initial 64K 
    minextents 1 
    maxextents unlimited 
); 

alter table MYTABLE 
    add constraint PK_SDATE primary key (SDATE) 
    using index 
    tablespace SYSTEM 
    pctfree 10 
    initrans 2 
    maxtrans 255 
    storage 
    (
    initial 64K 
    minextents 1 
    maxextents unlimited 
); 

负载表:

declare 
    i integer; 
begin 
    for i in 0 .. 100000 loop 
    insert into MYTABLE(sdate, cell, data) 
    values(sysdate - i/24, 'T' || i, i);  
    commit; 
    end loop; 
end; 

收集相关统计数据:

begin 
    dbms_stats.gather_table_stats(tabname => 'MYTABLE', ownname => 'SYS'); 
end; 

计划1:

enter image description here

计划2:

enter image description here

+0

表中有多少行?统计数据有多新鲜? – APC

+0

我的桌子有近100000行,数据很新;您可以通过创建一个只有一列或两列的简单表格轻松地重新生成问题,并亲自查看结果。 – RGO

+0

查询的费用是多少?你可以发布计划吗?我认为这个指数非常分散。 –

回答

11

的索引全扫描只能访问索引的一侧。当你在做

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE 

你正在请求访问双方。因此,如果您想同时使用最小和最大列值,索引全面扫描不可行。

更详细的分析你可以找到here

+0

您拥有的链接对此行为没有提供任何解释。它没有回答为什么索引不能(或不)被用来找到MIN和MAX。 –

+3

+1提供正确的答案,并链接到理查德·富特关于此主题的优秀博客文章。 –

+0

@ypercube ...关于如果它没有解释为什么索引全面扫描不适用于该查询,您认为在我给出的那个链接中所说的是什么?和tnx Rob van Wijk – avi

2

我不得不说,我没有看到相同的行为在11.2

如果我建立一个测试情况下,跟随和更新从10k到1m行响应文森特的评论

set linesize 130 
set pagesize 0 
create table mytable (sdate date); 

Table created. 

insert into mytable 
select sysdate - level 
    from dual 
connect by level <= 1000000; 
commit; 

1000000 rows created. 


Commit complete. 

alter table mytable add constraint pk_mytable primary key (sdate) using index; 

Table altered. 

begin 
dbms_stats.gather_table_stats(user, 'MYTABLE' 
          , estimate_percent => 100 
          , cascade => true 
           ); 
end; 
/

PL/SQL procedure successfully completed. 

然后,在执行您的疑问,我得到几乎相同寻找解释计划(注意不同类型的索引全扫描的)

explain plan for select min(sdate) from mytable; 

Explained. 

select * from table(dbms_xplan.display); 
Plan hash value: 3877058912 

----------------------------------------------------------------------------------------- 
| Id | Operation   | Name | Rows | Bytes | Cost (%CPU)| Time | 
----------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT  |  |  1 |  8 |  1 (0)| 00:00:01 | 
| 1 | SORT AGGREGATE  |  |  1 |  8 |  |  | 
| 2 | INDEX FULL SCAN (MIN/MAX)| PK_MYTABLE |  1 |  8 |  1 (0)| 00:00:01 | 
----------------------------------------------------------------------------------------- 

9 rows selected. 

explain plan for select min(sdate), max(sdate) from mytable; 

Explained. 

select * from table(dbms_xplan.display); 
Plan hash value: 3812733167 

------------------------------------------------------------------------------- 
| Id | Operation | Name  | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |  |  1 |  8 | 252 (0)| 00:00:04 | 
| 1 | SORT AGGREGATE |  |  1 |  8 |  |   | 
| 2 | INDEX FULL SCAN| PK_MYTABLE | 1000K| 7812K| 252 (0)| 00:00:04 | 
------------------------------------------------------------------------------- 

9 rows selected. 

从我的前面的回答引用:

的查询不使用索引的两个最常见原因是:

  1. 执行全表扫描更快。
  2. 糟糕的统计数字。

要不是有你没有的问题张贴我直接的答案是,你还没有收集在此表中统计的东西,你还没有足够高的估计%的收集他们或已使用analyze,不是帮助基于成本的优化器,不像dbms_stats.gather_table_stats

要从文件引用上analyze

对于大多数统计数据的收集,使用DBMS_STATS包, 它可以让你收集并行统计,收集全球 统计分区对象,和微调您的统计 以其他方式收集。有关DBMS_STATS程序包的更多信息,请参见Oracle数据库PL/SQL程序包和 类型参考。

使用Analyze语句(而不是DBMS_STATS)统计 收集不相关的基于成本的优化:

+0

我刚刚看到你评论说100k行,但重新做这个数量没有区别。 – Ben

+0

我在我的文章中提供了数据。我自己也做了一次,并得到了与11.1相同的结果。 – RGO

+0

10k is puny :)尝试使用1M行,您应该会看到差异 –

6

的解释计划不同:单一MINMAX会产生INDEX FULL SCAN (MIN/MAX),而当两个都存在,你会得到一个INDEX FULL SCANFAST FULL INDEX SCAN

理解上的差异,我们必须寻找一个FULL INDEX SCAN的描述:

在全索引扫描,数据库以便读取整个索引。

换句话说,如果索引上的VARCHAR2字段,Oracle将取,将含有例如以字母“A”开始的所有条目,并将由块中的所有读取块索引的第一块按字母顺序输入,直到最后输入(“A”到“Z”)。 Oracle可以用这种方式处理,因为这些条目是在二叉树索引中排序的。

当您在解释计划看INDEX FULL SCAN (MIN/MAX),这是使用的事实,因为条目的排序,你可以在读完后,第一个如果你只用MIN感兴趣停止优化的结果。如果您只对MAX感兴趣,Oracle可以使用相同的访问路径,但是这次从最后一个入口开始并从“Z”向后读取到“A”。

截止目前,FULL INDEX SCAN只有一个方向(向前或向后),并且不能同时从两端开始,这就是为什么当你要求min和max时,你会得到一个效率较低的访问方法。如其他答案所示,如果查询需要临界效率,则可以通过搜索两个不同查询中的最小值和最大值来运行自己的优化。

+0

我的第一个想法是为什么要进行完整索引扫描?为什么不寻求?但是,因为它在读取第一个值之后停止,那么这是有道理的,这只是一个步骤,而索引搜索将是多步走向b树。感谢解释的解释。 – Davos

4

尽量不要在一个查询中选择索引的两个边缘, 访问查询以不同的方式是这样的:在

select max_date, min_date 
from (select max(sdate) max_date from mytable), 
     (select min(sdate) min_date from mytable) 

将导致优化访问索引在INDEX_FULL_SCAN(MIN/MAX)嵌套循环(在我们的例子中,两次)。

enter image description here

+0

我会提出相同的解决方案,但你的答案不会令人惊讶,但是如何让sql引擎不够智能来自动解决这个问题,大声笑 – benjaminz

相关问题