【问题标题】:oracle : why index full scan is used for group by clause on non-leading index columnoracle:为什么索引全扫描用于非前导索引列的分组子句
【发布时间】:2020-07-23 03:36:39
【问题描述】:

我有一个表 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”列不是复合索引中的前导列,所以不能按排序顺序返回,为什么在这里使用索引全扫描?

【问题讨论】:

  • 您的查询没有WHEREHAVING 子句,这基本上意味着Oracle 必须读取表中的每条记录才能得到结果集。如果它使用索引,它一定会得出这样的结论:这样做会比暴力表扫描更快。但是,不要误以为索引有助于您的查询。这可能会有所帮助,但您的查询通常无法使用任何索引来改进。
  • 它使用了索引,我的问题是:为什么索引全扫描而不是索引快速全扫描?
  • 不可能是因为optimizer_index_cost_adj,因为优化器在某些情况下可能会ignore it for ffs
  • 将optimizer_index_cost_adj改成50后,依然使用索引全扫描,

标签: oracle indexing


【解决方案1】:

因复合索引而更新答案

我决定根据您指定复合索引的 cmets 更新答案。

  • 索引全扫描按 SORTED 顺序读取每个索引节点。
  • 索引快速全扫描用于以 UNSORTED 顺序从索引中检索表行。

索引全扫描:当 CBO 统计数据表明全索引扫描将比全表扫描更有效并且排序或组是在结果集上完成。当 CBO 确定查询将按索引顺序返回大量行时,通常会调用全索引扫描,并且全表扫描以及 sort 或 group by 选项可能会导致磁盘排序或散列操作转到临时表空间.

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

在您的具体情况下,它应该使用 FAST FULL SCAN,但可能是统计问题或 optimizer_index_cost_adj 参数值太低。

让我给你看一个测试用例。我创建了一个表,其中 id 字段生成为身份,一个字段包含性别,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 |
-----------------------------------------------------------------------------------

在我的示例中,由于 optimizer_index_cost_adj 的值,它使用 FAST FULL SCAN

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 会使索引访问路径看起来是正常开销的一半。

【讨论】:

  • 感谢您的解释,由于索引是(id,sex),那么索引读取的结果将按id排序,而不是按性别排序,为什么优化器只使用索引快速全扫描来获取快速出结果,然后进行排序,
  • 所以索引是复合的,好吧,让我建立一个测试用例,我会发布它
  • @tonyibm,我发布了一个包含一些细节的测试用例。希望它能澄清更多的问题
  • 感谢您的测试用例,我没有更改 optimizer_index_cost_adj,它的默认值为 100,但优化器仍然使用索引全扫描,为什么?
  • 您可以在会话级别更改该值,如果您收集统计信息,您将看到它如何进入索引快速全面扫描。它将进行索引全扫描,因为优化器索引成本 adj 为 100,在这种情况下,索引全扫描与索引快速全扫描中的评估看起来相同,Oracle 将始终使用后者。如果您认为快速全扫描会更快,则切换此语句的会话级别或使用提示更改优化器索引成本调整
猜你喜欢
  • 2020-09-09
  • 2015-01-27
  • 2013-10-01
  • 2014-12-18
  • 2013-01-07
  • 2023-03-14
  • 1970-01-01
  • 2020-12-04
  • 1970-01-01
相关资源
最近更新 更多