27

我有一个MYTABLE带有日期列的表,SDATE它是表的主键,并且上面有一个唯一索引。

当我运行此查询时:

SELECT MIN(SDATE) FROM MYTABLE

它立即给出答案。同样的情况发生在:

SELECT MAX(SDATE) FROM MYTABLE

但是,如果我一起查询:

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

执行需要更多时间。我分析了计划,发现当查询 min 或 max 之一时,它使用 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:

在此处输入图像描述

计划2:

在此处输入图像描述

4

4 回答 4

12

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

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

您要求访问 2 个方面。因此,如果您想要最小和最大列值,索引全扫描是不可行的。

您可以在此处找到更详细的分析。

于 2012-09-24T13:33:43.960 回答
6

解释计划是不同的:一个MINorMAX将产生 aINDEX FULL SCAN (MIN/MAX)而当两个存在时,您将得到一个INDEX FULL SCANor a FAST FULL INDEX SCAN

为了理解差异,我们必须寻找对 a 的描述FULL INDEX SCAN

在全索引扫描中,数据库按顺序读取整个索引。

换句话说,如果索引在一个VARCHAR2字段上,Oracle 将获取索引的第一个块,该块将包含例如以字母“A”开头的所有条目,并将按字母顺序逐块读取所有条目,直到最后一个条目( “A”到“Z”)。Oracle 可以这样处理,因为条目是在二叉树索引中排序的。

当您INDEX FULL SCAN (MIN/MAX)在解释计划中看到时,这是优化的结果,该优化使用了这样一个事实:由于条目已排序,如果您只对MIN. 如果您对MAX唯一感兴趣,Oracle 可以使用相同的访问路径,但这次从最后一个条目开始,从“Z”向后读取到“A”。

到目前为止,aFULL INDEX SCAN只有一个方向(向前或向后)并且不能同时从两端开始,这就是为什么当您同时要求最小值和最大值时,您会得到一种效率较低的访问方法。

正如其他答案所建议的那样,如果查询需要关键的效率,您可以通过在两个不同的查询中搜索最小值和最大值来运行自己的优化。

于 2012-09-24T14:16:58.310 回答
5

尝试不要在一个查询中选择索引的两个边缘,以不同的方式访问查询,如下所示:

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) 中的索引(在我们的例子中,两次)。

在此处输入图像描述

于 2012-09-25T14:49:22.117 回答
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.

然后,执行您的查询,我得到几乎相同的解释计划(注意不同类型的 INDEX FULL SCAN)

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)进行与基于成本的优化器无关的统计信息收集:

于 2012-09-24T13:26:27.700 回答