【问题标题】:Why am I getting an index scan for a covered query using aggregate function?为什么我会使用聚合函数对覆盖查询进行索引扫描?
【发布时间】:2011-06-19 10:01:12
【问题描述】:

我有一个问题:

select min(timestamp) from table

这个表有 60+百万行,我每天都会删除一些。要确定是否有任何数据足够旧,请删除我运行上面的查询。时间戳升序有一个索引,只包含一列,oracle中的查询计划导致这是一次全索引扫描。这不应该是搜索的定义吗?

编辑包括计划:

| Id  | Operation                  | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
|   2 |   INDEX FULL SCAN (MIN/MAX)| NEVENTS_I2 |     1 |     8 |     4   (100)| 00:00:01 |
|   1 |  SORT AGGREGATE            |            |     1 |     8 |            |          |
|   0 | SELECT STATEMENT           |            |     1 |     8 |     4   (0)| 00:00:01 |

【问题讨论】:

  • “全扫描”我假设您的意思是索引全扫描,而不是表扫描,对吗?
  • 这么慢吗?您的响应时间是多少?
  • 索引完整扫描实际上是没有上限或下限的范围扫描。该查询将仅扫描所需的行数。因此,虽然它说的是全扫描,但它实际上并不会读取索引的每个块。它从一端开始,只会向下读取到第一个叶子块,然后它就会完成。

标签: database performance oracle indexing


【解决方案1】:

起初我认为只有当列被声明为 NOT NULL 时才会使用索引。我使用以下设置进行了测试:

SQL> CREATE TABLE my_table (ts TIMESTAMP);

Table created

SQL> INSERT INTO my_table
  2  SELECT systimestamp + ROWNUM * INTERVAL '1' SECOND 
  3    FROM dual CONNECT BY LEVEL <= 100000;

100000 rows inserted

SQL> CREATE INDEX ix ON my_table(ts);

Index created

SQL> EXPLAIN PLAN FOR SELECT MIN(ts) FROM my_table;

Explained

SQL> SELECT * FROM TABLE(dbms_xplan.display);

--------------------------------------------------------------------------------
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |      |     1 |    13 |    69   (2)| 00:00:0
|   1 |  SORT AGGREGATE            |      |     1 |    13 |            |
|   2 |   INDEX FULL SCAN (MIN/MAX)| IX   | 90958 |  1154K|            |
--------------------------------------------------------------------------------

这里我们注意到索引被使用了,但是索引中的所有行都被读取了。如果我们指定该列不为空,我们会得到一个更好的计划:

SQL> ALTER TABLE my_table MODIFY ts NOT NULL;

Table altered

SQL> EXPLAIN PLAN FOR SELECT MIN(ts) FROM my_table;

Explained

SQL> SELECT * FROM TABLE(dbms_xplan.display);

--------------------------------------------------------------------------------
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |      |     1 |    13 |     2   (0)| 00:00:0
|   1 |  SORT AGGREGATE            |      |     1 |    13 |            |
|   2 |   INDEX FULL SCAN (MIN/MAX)| IX   | 90958 |  1154K|     2   (0)| 00:00:0
--------------------------------------------------------------------------------

事实上,如果我们添加 WHERE 子句(Oracle 将从索引中读取单行),也会使用相同的计划:

SQL> EXPLAIN PLAN FOR SELECT MIN(ts) FROM my_table WHERE ts IS NOT NULL;

Explained

SQL> SELECT * FROM TABLE(dbms_xplan.display);

--------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |     1 |    13 |     2   (0)| 00:00:
|   1 |  SORT AGGREGATE             |      |     1 |    13 |            |
|   2 |   FIRST ROW                 |      | 90958 |  1154K|     2   (0)| 00:00:
|   3 |    INDEX FULL SCAN (MIN/MAX)| IX   | 90958 |  1154K|     2   (0)| 00:00:
--------------------------------------------------------------------------------

最后一个计划显示(第 2 行)Oracle 确实在执行“搜索”。

【讨论】:

  • 有问题的列不可为空,感谢解释计划 btw 的文本结果
【解决方案2】:

你能发布实际的查询计划吗?您确定它没有进行最小/最大索引全扫描吗?正如您在此示例中所见,我们使用最小/最大索引全扫描从 100,000 行表中获取 MIN 值,并且只有少数一致的获取。

SQL> create table foo (
  2    col1 date not null
  3  );

Table created.

SQL> insert into foo
  2    select sysdate + level
  3      from dual
  4   connect by level <= 100000;

100000 rows created.

SQL> create index idx_foo_col1
  2      on foo( col1 );

Index created.

SQL> analyze table foo compute statistics for all indexed columns;

Table analyzed.

SQL> set autotrace on;

<<Note that I ran this statement once just to get the delayed block cleanout to 
  happen so that the consistent gets number wouldn't be skewed.  You could run a
  different query as well>>

  1* select min(col1) from foo
SQL> /

MIN(COL1)
---------
02-FEB-11


Execution Plan
----------------------------------------------------------
Plan hash value: 817909383

--------------------------------------------------------------------------------

-----------

| Id  | Operation                  | Name         | Rows  | Bytes | Cost (%CPU)|

 Time     |

--------------------------------------------------------------------------------

-----------

|   0 | SELECT STATEMENT           |              |     1 |     7 |     2   (0)|

 00:00:01 |

|   1 |  SORT AGGREGATE            |              |     1 |     7 |            |

          |

|   2 |   INDEX FULL SCAN (MIN/MAX)| IDX_FOO_COL1 |     1 |     7 |     2   (0)|

 00:00:01 |

--------------------------------------------------------------------------------

-----------


Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        532  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

【讨论】:

    【解决方案3】:

    只是想深入了解“INDEX FULL SCAN (MIN/MAX)”与“INDEX FULL SCAN”完全不同的事实。 INDEX FULL SCAN 确实会扫描整个索引(可能带有过滤)。但是,INDEX FULL SCAN (MIN/MAX) 或 INDEX RANGE SCAN (MIN/MAX) 只能获取最小或最大的叶块(来自范围),但只能在列不为空的情况下使用(这是有点傻,而且确实是一个错误,因为 NULL 值根据定义既不是最小值也不是最大值)。 (MIN/MAX) 优化是一个隐式的 FIRST_ROWS 操作,不需要“WHERE ... IS NOT NULL”查询条件来执行优化。有趣的是,对于基于函数的索引,CBO 通常不会考虑 MIN/MAX 优化,这是另一个小错误。

    【讨论】:

      猜你喜欢
      • 2013-11-28
      • 2010-11-12
      • 2015-03-18
      • 2017-06-12
      • 2015-01-27
      • 2021-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多