索引扫描导致大量物理读取(Oracle)

Index scan results in a lot of physical reads (Oracle)

有两个table有超过2000万条记录。数据库版本为Oracle 11.2.0.3.13标准版

SQL> select count(*) from atsd_tra_trade_print;

  COUNT(*)
----------
  20000000

SQL> select count(*) from atsd_mob_allocation_pos;

  COUNT(*)
----------
  20551780

我们 运行 对 table 进行了以下查询。 查询结构相似。查询用于分页。 两个查询的排序列都已编入索引并且不包含任何 NULL 值。下面给出了查询和相应的自动跟踪输出/计划。

索引 INDX_ATTP_6 创建于 ATSD_TRA_TRADE_PRINT(TRANSACT_TIME)

索引 INDX_AMAP_6 创建于 ATSD_MOB_SETTLEMENT_POS(DATE_TIME)

索引基数如下

INDX_ATTP_6 - 5972

INDX_AMAP_6 - 5974

上面两列的数据类型都是varchar2.

在刷新 buffer_cache 之后,两个查询都是 运行 。还收集了架构统计信息。

SELECT * FROM ( SELECT T2.*, ROWNUM AS RN FROM (
SELECT *
FROM ATSDN_TRA_TRADE_PRINT T
WHERE LATEST = 1
ORDER BY TRANSACT_TIME) T2)
WHERE RN > 0 and ROWNUM <= 28;

输出:

28 rows selected.

Elapsed: 00:00:00.16

Execution Plan
----------------------------------------------------------
Plan hash value: 3823692003

-------------------------------------------------------------------------    -------------------------------
| Id  | Operation           | Name             | Rows  | Bytes | Cost     (%CPU)| Time     |
-------------------------------------------------------------------------    -------------------------------
|   0 | SELECT STATEMENT        |              |    28 | 60060 |     8       (0)| 00:00:01 |
|*  1 |  COUNT STOPKEY          |              |       |       |        |              |
|*  2 |   VIEW              |              |    28 | 60060 |     8   (0)|     00:00:01 |
|   3 |    COUNT            |              |       |       |        |             |
|   4 |     VIEW            |              |    28 | 59696 |     8   (0)|     00:00:01 |
|*  5 |      TABLE ACCESS BY INDEX ROWID| ATSD_TRA_TRADE_PRINT |  7488K|      3234M|     8   (0)| 00:00:01 |
|   6 |       INDEX FULL SCAN       | INDX_ATTP_6          |    57 |           |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------    -------------------------------

Predicate Information (identified by operation id):
 ---------------------------------------------------

   1 - filter(ROWNUM<=28)
   2 - filter("RN">0)
   5 - filter("LATEST"=1)


Statistics
----------------------------------------------------------
    571  recursive calls
      0  db block gets
    948  consistent gets
     49  physical reads
      0  redo size
  18586  bytes sent via SQL*Net to client
    535  bytes received via SQL*Net from client
      3  SQL*Net roundtrips to/from client
     29  sorts (memory)
      0  sorts (disk)
     28  rows processed

以上查询按预期执行得很快。但是另一个 table 上的查询导致执行时间很长。我能看到的唯一观察是有大量的物理读取。 两个查询执行计划看起来也很相似。

SELECT * FROM ( SELECT T2.*, ROWNUM as RN FROM ( 
SELECT *
FROM ATSD_MOB_ALLOCATION_POS AL
WHERE  LATEST = 1
ORDER BY DATE_TIME) T2)
WHERE RN > 0 and ROWNUM <= 28;

输出:

28 rows selected.

Elapsed: 00:01:52.28

Execution Plan
----------------------------------------------------------
Plan hash value: 884170602

-------------------------------------------------------------------------    ----------------------------------
| Id  | Operation           | Name            | Rows  | Bytes | Cost     (%CPU)| Time     |
-------------------------------------------------------------------------    ----------------------------------
|   0 | SELECT STATEMENT        |             |    28 | 38892 | 7   (0)|     00:00:01 |
|*  1 |  COUNT STOPKEY          |             |   |   |        |      |
|*  2 |   VIEW              |             |    28 | 38892 | 7   (0)|     00:00:01 |
|   3 |    COUNT            |             |   |   |        |      |
|   4 |     VIEW            |             |    28 | 38528 | 7   (0)|     00:00:01 |
|*  5 |      TABLE ACCESS BY INDEX ROWID| ATSD_MOB_ALLOCATION_POS |        14M|  5863M| 7   (0)| 00:00:01 |
|   6 |       INDEX FULL SCAN       | INDX_AMAP_6         |    39 |   | 4       (0)| 00:00:01 |
-------------------------------------------------------------------------    ----------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=28)
   2 - filter("RN">0)
   5 - filter("LATEST"=1)


Statistics
----------------------------------------------------------
    281  recursive calls
      0  db block gets
 335395  consistent gets
 332763  physical reads
    116  redo size
  13582  bytes sent via SQL*Net to client
    535  bytes received via SQL*Net from client
      3  SQL*Net roundtrips to/from client
     17  sorts (memory)
      0  sorts (disk)
     28  rows processed

谁能告诉我为什么会这样。提前致谢...:)

问题出在索引结构上......当它以 asc 方式遍历索引 INDX_AMAP_6 时,它必须读取 500 万条记录才能遇到满足过滤条件的行 "LATEST =1".排序列和文件管理器列上的复合索引解决了该问题。

在 atsd_mob_allocation_pos 上创建索引 AMAP_TEST1(最新,date_time);