Usefulness of a multi-attribute index

Cel*_*tas 2 index physical-design

If an index has more than one attribute in it, is there any speed gained in a select statement whose where clause uses one of the attributes in the index?

例如,拿一个T带有属性索引的表ab。索引是否对查询有用:

select * from T where a='foo'
Run Code Online (Sandbox Code Playgroud)

我问是因为我正在阅读的书有以下陈述,我无法理解:

如果多属性索引的键确实是按某种顺序连接的属性,那么我们甚至可以使用此索引在第一个属性中查找具有给定值的所有元组。

Mat*_*Mat 5

例如,以属性 a 和 b 上有索引的表 T 为例。索引是否对查询有用:

select * from T where a='foo'
Run Code Online (Sandbox Code Playgroud)

那里有两个问题:

  • 可以(a,b)为此查询使用索引吗?

    答案通常是肯定的。也许并非所有数据库都具有这种能力,但大多数主流数据库都有 AFAIK。将选择替换为select a,b from ...,引擎不仅可以使用索引,而且根本无法访问实际表来回答查询。

  • 优化器会选择使用索引吗?

    这将取决于数据库系统以及优化器对数据的了解程度。如果它可以确定第一列“足够有选择性”,那么它很可能会使用它。如果没有,很可能不会。

这是有关 Oracle XE 11g 的插图。

SQL> create table T
  2  as select object_name a, rownum b, rownum c
  3  from all_objects;
Table created.
SQL> create index T_ab on T(a,b);
Index created.

SQL> exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'T');
PL/SQL procedure successfully completed.

SQL> set autotrace traceonly explain
SQL> select * from T where a = 'foo';

Execution Plan
------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    27 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T    |     1 |    27 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN      | T_AB |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

all_objects视图包含的所有对象(表,视图,存储过程,等等)存在于数据库中的信息。该object_name领域是不是唯一的,但目前还没有大量重复的(在此数据库中至少),让领先的领域本身是非常有选择性。
对索引进行范围扫描,然后通过 rowid 查找估计的一行的成本将比进行全表扫描的成本低得多,因此优化器采用了这条路线。
这是一个相当常见的情况和计划,您可能会遇到很多(或其他数据库引擎上的类似情况)。

现在这里有一个不同的场景,优化器可以使用列的实际内容的详细图片来以不同的方式优化事物:

SQL> create table Q
  2  as select 'a' a, rownum b, rownum c
  3  from all_objects;
Table created.
SQL> create index Q_ab on Q(a,b);
Index created.

SQL> exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'Q');
PL/SQL procedure successfully completed.

SQL> set autotrace traceonly explain
SQL> select * from Q where a = 'a';

Execution Plan
--------------------------------------------------------------------------
| Id  | Operation     | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |  | 18085 |   176K|    15   (7)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| Q    | 18085 |   176K|    15   (7)| 00:00:01 |
--------------------------------------------------------------------------

SQL> select * from Q where a = 'z';

Execution Plan
------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    10 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| Q    |     1 |    10 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN      | Q_AB |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

该表故意完全倾斜,第一列具有'a'唯一的值。如果优化器知道这一点,那么它可以根据实际查询的键值选择不同的路径,如上所示。
如果'a'被要求,使用索引是一个糟糕的举动 - 您需要遍历整个索引整个表以获取所有行,这比仅扫描表(可能很多)成本更高。
如果请求的值不是'a',则扫描索引的效率要高得多,因为它可能不会返回任何行。

这里有一些可能更令人惊讶的事情:(a,b)where子句b仅过滤时,实际上可以使用索引 on 。

SQL> select * from Q where b = 1000;

Execution Plan
------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    10 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| Q    |     1 |    10 |     3   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN       | Q_AB |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为前导列几乎没有不同的值,但第二列本质上是唯一的。将其视为优化器按第一列对索引进行分区,然后对每个分区进行二分搜索。如果分区数量很少并且其他标准相当有选择性,那么这将是有效的。(即T在本例中不适用于表格。)