oracle:为什么对非前导索引列上的GROUPBY子句使用索引完全扫描

oracle:为什么对非前导索引列上的GROUPBY子句使用索引完全扫描,oracle,indexing,Oracle,Indexing,我有一个表EMPLOYEE,一个关于(id,sex)的普通综合索引 SQL语句如下所示: select sex,count(*) from employee group by sex; Plan hash value: 1246558535 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows |

我有一个表EMPLOYEE,一个关于(id,sex)的普通综合索引

SQL语句如下所示:

select sex,count(*) from employee group by sex;

Plan hash value: 1246558535

-------------------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |     2 |     4 |     2  (50)| 00:00:01 |
|   1 |  HASH GROUP BY   |            |     2 |     4 |     2  (50)| 00:00:01 |
|   2 |   INDEX FULL SCAN| IDX_ID_SEX |   104 |   208 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------

----------------------------------------------------------
     11  recursive calls
      0  db block gets
     11  consistent gets
      0  physical reads
      0  redo size
    673  bytes sent via SQL*Net to client
    552  bytes received via SQL*Net from client
      2  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
      2  rows processed
使用索引完全扫描,我很好奇:
为什么使用索引完全扫描而不是索引快速完全扫描?我的理解是:对于索引完全扫描,数据是按排序顺序返回的,因为“sex”列不是复合索引中的前导列,它不能按排序顺序返回,为什么在这里使用索引完全扫描?

根据复合索引更新答案

我决定根据您指定索引为复合索引的注释更新答案

  • 索引完全扫描按排序顺序读取每个索引节点
  • 索引快速完全扫描用于按未排序的顺序从索引中检索表行
索引完整扫描:当CBO统计数据表明完整索引扫描将比完整表扫描更有效,并且对结果集进行排序或分组时,Oracle将选择索引完整扫描。当CBO确定查询将按索引顺序返回多行时,通常会调用完整索引扫描,而完整表扫描和排序或分组方式选项可能会导致磁盘排序或哈希操作转到临时表空间

快速全索引扫描 当索引包含满足查询所需的所有值且不需要表访问时,将调用此执行计划。快速完整索引扫描执行计划将通过多块读取(使用db_文件_多块_读取计数)读取整个索引,并以未排序的顺序返回行

在您的特定情况下,它应该使用快速完全扫描,但可能是统计数据中的问题,或者是优化器_index_cost_adj参数值太低

让我给你看一个测试用例。我创建了一个表,其中id字段生成为identity,一个字段的性别为M或F,列c1为随机字符串

SQL> create table test_index ( c1 varchar2(10), c2 number generated always as identity, c3 varchar2(1) );

Table created.
填充200000条记录并在c2和c3上创建索引组合

SQL> declare
  2  begin
  3  for r in 1 .. 100000
  4  loop
  5      insert into test_index ( c1 , c3 ) values ( dbms_random.string('U',1
  6      insert into test_index ( c1 , c3 ) values ( dbms_random.string('U',1
  7  end loop;
  8  commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> create index idx_test_index on test_index (c2 , c3) ;

Index created.
现在,让我们看看它的行为

SQL> select count(*) from cpl_rep.test_index ;

  COUNT(*)
----------
    200000

SQL> exec dbms_stats.gather_table_stats ( 'MYOWNER', 'TEST_INDEX' , cascade => true );

PL/SQL procedure successfully completed.


SQL> set autotrace traceonly explain

SQL> set lines 200
SQL> select c3, count(*) from test_index group by c3

Execution Plan
----------------------------------------------------------
Plan hash value: 805205005

----------------------------------------------------------------------------------------
| Id  | Operation             | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                |     2 |     4 |   102   (8)| 00:00:01 |
|   1 |  HASH GROUP BY        |                |     2 |     4 |   102   (8)| 00:00:01 |
|   2 |   INDEX FAST FULL SCAN| IDX_TEST_INDEX |   200K|   390K|    95   (2)| 00:00:01 |
----------------------------------------------------------------------------------------

SQL> select /*+index ( a IDX_TEST_INDEX ) */ c3, count(*) from myowner.test_index a group by c3
  2  ;

Execution Plan
----------------------------------------------------------
Plan hash value: 2605845939

-----------------------------------------------------------------------------------
| Id  | Operation        | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                |     2 |     4 |   257   (4)| 00:00:01 |
|   1 |  HASH GROUP BY   |                |     2 |     4 |   257   (4)| 00:00:01 |
|   2 |   INDEX FULL SCAN| IDX_TEST_INDEX |   200K|   390K|   250   (1)| 00:00:01 |
-----------------------------------------------------------------------------------
在我的示例中,它使用快速完全扫描,因为优化器_index_cost_adj的值

SQL> set autotrace off
SQL> show parameter index_cost_adj

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_index_cost_adj             integer     100
让我们将其更改为一个较低的值,看看会发生什么

SQL> alter session set optimizer_index_cost_adj=20 ;

Session altered.

SQL> set autotrace traceonly explain
SQL> select c3, count(*) from cpl_rep.test_index group by c3 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 2605845939

-----------------------------------------------------------------------------------
| Id  | Operation        | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                |     2 |     4 |    57  (13)| 00:00:01 |
|   1 |  HASH GROUP BY   |                |     2 |     4 |    57  (13)| 00:00:01 |
|   2 |   INDEX FULL SCAN| IDX_TEST_INDEX |   200K|   390K|    50   (0)| 00:00:01 |
-----------------------------------------------------------------------------------
创建optimizer_index_cost_adj参数是为了允许更改完整扫描与索引操作的相对成本。这是所有参数中最重要的参数,默认设置为100对于大多数Oracle系统是不正确的。它允许您将访问路径选择的优化器行为调整为或多或少索引友好,也就是说,使优化器或多或少倾向于通过完整表扫描或完整索引扫描来选择索引访问路径


此参数的默认值为100%,在该值下,优化器以常规成本计算索引访问路径。任何其他值都会使优化器按照常规成本的百分比评估访问路径。例如,设置为50会使索引访问路径看起来比正常情况贵一半。

您的查询没有
WHERE
HAVING
子句,这基本上意味着Oracle必须读取表中的每条记录才能获得结果集。如果它使用的是索引,那么它肯定已经得出结论,这样做将比仅仅使用蛮力表扫描更快。但是,不要错误地认为索引有助于查询。这可能会有一点帮助,但您的查询通常无法使用任何索引进行改进。它使用了索引,我的问题是:为什么索引完全扫描而不是索引快速完全扫描?这难道不是因为优化器在某些情况下可能在我将优化器_index_cost_adj更改为50后,它仍然使用索引完全扫描,感谢您的解释,由于索引是(id,sex),那么从索引读取的结果将按id排序,而不是按性别排序,为什么优化器只是使用索引快速完全扫描来快速获得结果,然后进行排序,所以索引是复合的,好吧,让我构建一个测试用例,然后发布it@tonyibm,我发布了一个带有一些细节的测试用例。希望它能为您的测试用例澄清更多的问题,我没有更改optimizer_index_cost_adj,它的默认值为100,但optimizer仍然使用索引完全扫描,为什么?您可以在会话级别更改该值,如果您收集统计信息,您将看到它如何进入索引快速完全扫描。它将进行索引完全扫描,因为optimizer index cost adj为100,在这种情况下,索引完全扫描中的评估结果与索引快速完全扫描中的评估结果相同,Oracle将始终使用后者。如果您认为在快速全扫描中它会更快,那么为该语句切换会话级别,或者使用提示更改优化器索引开销ADJ。