我有一个表MYTABLE
,其日期列SDATE
是表的主键,并且在其上有唯一索引。
当我运行此查询时:
SELECT MIN(SDATE) FROM MYTABLE
它立即给出答案。同样的情况发生在:
SELECT MAX(SDATE) FROM MYTABLE
但是,如果我一起查询:
SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE
执行需要更多时间。我分析了计划并发现当查询最小值或最大值时,它使用INDEX FULL SCAN(MIN / MAX),但是当两者同时被查询时,它会进行全表扫描。
为什么?
测试数据:
版本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:
答案 0 :(得分:12)
索引完全扫描只能访问索引的一侧。当你在做什么
SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE
您要求访问双方。因此,如果您同时需要最小和最大列值,则索引完全扫描不可行。
您可以找到更详细的分析here。
答案 1 :(得分:6)
解释计划有所不同:单个MIN
或MAX
会生成INDEX FULL SCAN (MIN/MAX)
,而当两个人出现时,您将获得INDEX FULL SCAN
或{{3 }}
要了解其中的差异,我们必须查找FAST FULL INDEX SCAN
的说明:
在完整索引扫描中,数据库按顺序读取整个索引。
换句话说,如果索引位于VARCHAR2
字段上,Oracle将获取索引的第一个块,该块将包含例如以字母“A”开头的所有条目,并将逐块读取所有条目按字母顺序排列,直到最后一个条目(“A”到“Z”)。 Oracle可以这种方式处理,因为条目是在二叉树索引中排序的。
当您在解释计划中看到INDEX FULL SCAN (MIN/MAX)
时,这是优化的结果,该优化使用以下事实:由于条目已排序,如果您只对第一个条目感兴趣,则可以在阅读第一个条目后停止MIN
。如果您只对MAX
感兴趣,Oracle可以使用相同的访问路径,但这次从最后一个条目开始,从“Z”向后读取到“A”。
截至目前,FULL INDEX SCAN
只有一个方向(向前或向后)并且不能同时从两端开始,这就是为什么当你要求最小值和最大值时,你会得到一个更少的高效的访问方法。
正如其他答案所建议的那样,如果查询需要关键效率,您可以通过在两个不同的查询中搜索最小值和最大值来运行自己的优化。
答案 2 :(得分: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)中的索引(在我们的例子中,两次)。
答案 3 :(得分:2)
我不得不说我在11.2中没有看到相同的行为
如果我按如下方式设置测试用例,并根据Vincent的评论从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.
引用我之前的答案:
查询不使用索引的两个最常见原因是:
- 进行全表扫描更快。
- 统计数据不佳。
醇>
除非您在问题中没有发布任何内容,否则我的直接答案是您没有收集此表的统计信息,您没有使用足够高的估算百分比收集它们,或者您已使用{{3}与analyze
不同,不会帮助基于费用的优化工具。
引用analyze
上的文档:
要收集大多数统计信息,请使用DBMS_STATS包, 它允许您并行收集统计信息,收集全局信息 分区对象的统计信息,并微调统计信息 以其他方式收集。请参阅Oracle Database PL / SQL包和 类型参考有关DBMS_STATS包的更多信息。
使用ANALYZE语句(而不是DBMS_STATS)进行统计 与基于成本的优化器无关的集合: