通过使用索引加速使用聚合函数的查询

时间:2018-06-14 18:42:49

标签: sql oracle indexing

根据教科书,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; 

编辑:在分析了这两个查询的执行计划之后,第一个查询似乎利用了索引,而第二个查询则没有。

1 个答案:

答案 0 :(得分:0)

索引至少有三种不同的方式可以提高汇总查询效果:INDEX FULL SCAN (MIN/MAX)INDEX FAST FULL SCANINDEX FULL SCAN / SORT GROUP BY NOSORT

指数结构

在考虑不同的索引算法时,可能有助于记住B树索引的内部结构的这个图像:

enter image description here

(虽然它不是这个答案的完美匹配,因为它只显示单列索引。您可以将多列索引视为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可能无法使用某些索引,因为索引并不总是存储空值)
  • 有一个关于薪水的索引和关于department_id和薪水的多列索引
  • 表格数据有点偏差。工资值相对独特,但只有100个department_id值。

INDEX FULL SCAN(MIN / MAX)

当本书说索引支持MAXMIN函数时,它可能指的是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可以非常快地获得MINMAX,但由于某种原因,当它必须找到这两个值时它会发生绊倒。看起来它应该是一个足够简单的功能来遍历左侧和右侧,但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 |
--------------------------------------------------------------------------------------

INDEX FAST FULL SCAN

对于第二个查询,使用多列,多列索引可以提供帮助。索引包含所有数据,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 |
-----------------------------------------------------------------------------------------------

按NOSORT指数完全扫描/分类

对于第二个查询,我们基本上没有尝试多次为每个部门运行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几乎总是有办法加速任何查询,尽管创建这些单独的对象总是有成本。