根据教科书,Oracle数据库12c完整参考文献Bob Bryla和Kevin Loney,“除了支持WHERE子句和连接,索引还支持ORDER BY子句以及MAX和MIN函数。”他们后来继续说,“如果选择索引列的eh MAX或MIN值,优化器可以使用索引快速找到列的最大值或最小值。”
为了澄清这些引用,我们假设在名为EMPS的表的salary列上没有索引。如果工资栏中有索引,则以下查询是否会受益:
select min(salary)
from emps;
select department_id, min(salary)
from emps
group by department_id;
编辑:在分析了这两个查询的执行计划之后,第一个查询似乎利用了索引,而第二个查询则没有。
答案 0 :(得分:0)
索引至少有三种不同的方式可以提高汇总查询效果:INDEX FULL SCAN (MIN/MAX)
,INDEX FAST FULL SCAN
和INDEX FULL SCAN / SORT GROUP BY NOSORT
。
在考虑不同的索引算法时,可能有助于记住B树索引的内部结构的这个图像:
(虽然它不是这个答案的完美匹配,因为它只显示单列索引。您可以将多列索引视为B树内的B树。)
drop table emps;
create table emps
(
id number not null,
name varchar2(100) not null,
department_id number not null,
salary number not null
);
create index emps_sal_idx on emps(salary);
create index emps_dept_and_sal_idx on emps(department_id, salary);
insert into emps
select
level id,
'John Smith '||level name,
mod(level, 100) department_id,
50000 + dbms_random.value * 100000 salary
from dual
connect by level <= 100000;
begin
dbms_stats.gather_table_stats(user, 'EMPS');
end;
/
有关此表,索引和数据的一些重要注意事项:
not null
。否则Oracle可能无法使用某些索引,因为索引并不总是存储空值)当本书说索引支持MAX
和MIN
函数时,它可能指的是INDEX FULL SCAN (MIN/MAX)
操作。查看上图中数据的排序和组织方式。查找第一个或最后一个值是一个非常简单的操作;多次按左手边找到最小值,或多次按右手边找到最大值。
explain plan for
select min(salary)
from emps;
select * from table(dbms_xplan.display);
Plan hash value: 293008466
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 22 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 22 | | |
| 2 | INDEX FULL SCAN (MIN/MAX)| EMPS_SAL_IDX | 1 | 22 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
但有时会出现问题。 Oracle可以非常快地获得MIN
或MAX
,但由于某种原因,当它必须找到这两个值时它会发生绊倒。看起来它应该是一个足够简单的功能来遍历左侧和右侧,但Oracle必须在这些情况下读取整个索引。
explain plan for
select min(salary), max(salary)
from emps;
select * from table(dbms_xplan.display);
Plan hash value: 2011972768
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 22 | 162 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 22 | | |
| 2 | INDEX FAST FULL SCAN| EMPS_SAL_IDX | 100K| 2148K| 162 (1)| 00:00:01 |
--------------------------------------------------------------------------------------
对于第二个查询,使用多列,多列索引可以提供帮助。索引包含所有数据,Oracle可以直接从较小的索引读取而不是读取整个大表。这是INDEX FAST FULL SCAN
操作。
explain plan for
select department_id, min(salary)
from emps
group by department_id;
select * from table(dbms_xplan.display);
Plan hash value: 104291853
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 2500 | 170 (3)| 00:00:01 |
| 1 | HASH GROUP BY | | 100 | 2500 | 170 (3)| 00:00:01 |
| 2 | INDEX FAST FULL SCAN| EMPS_DEPT_AND_SAL_IDX | 100K| 2441K| 166 (1)| 00:00:01 |
-----------------------------------------------------------------------------------------------
对于第二个查询,我们基本上没有尝试多次为每个部门运行MIN / MAX扫描? Oracle并没有那么精确的操作,但是它与INDEX FULL SCAN
后跟SORT GROUP BY NOSORT
的组合很接近。如果Oracle按顺序读取索引,则不必对任何内容进行排序,也不需要编写任何临时空间来查找最小值和最大值。每个部门都很容易找到最小值和最大值。
explain plan for
select /*+ index(emps) */ department_id, min(salary)
from emps
group by department_id;
select * from table(dbms_xplan.display);
Plan hash value: 445731427
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 2500 | 607 (1)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 100 | 2500 | 607 (1)| 00:00:01 |
| 2 | INDEX FULL SCAN | EMPS_DEPT_AND_SAL_IDX | 100K| 2441K| 607 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------
(我需要提示让上述计划有效。我不确定为什么,由于某些原因,甲骨文认为它不如其他方式好。而且可能会慢一些。全表扫描和快速完整索引扫描可以使用多块读取,而索引范围和完全扫描使用单块读取,有时比智能方式更难以做事。)
使用内存(列中存储的数据),使用本地索引的列表间隔分区(可能然后它可以对每个索引分区使用最小/最大扫描?)或其他一些提升性能可能有其他聪明的方法功能我没有想到。这里的教训是Oracle几乎总是有办法加速任何查询,尽管创建这些单独的对象总是有成本。