【问题标题】:Why is Oracle ignoring index with ORDER BY?为什么 Oracle 使用 ORDER BY 忽略索引?
【发布时间】:2012-11-09 23:27:12
【问题描述】:

我的目的是获取客户的分页结果集。我正在使用这个算法,来自Tom:

select * from (
  select /*+ FIRST_ROWS(20) */ FIRST_NAME, ROW_NUMBER() over (order by FIRST_NAME) RN
  from CUSTOMER C
)
where RN between 1 and 20
order by RN;

我还在“客户”列上定义了一个索引。“FIRST_NAME”:

CREATE INDEX CUSTOMER_FIRST_NAME_TEST ON CUSTOMER (FIRST_NAME ASC);

查询返回预期的结果集,但从解释计划中我注意到未使用索引:

--------------------------------------------------------------------------------------
| Id  | Operation                 | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |          | 15467 |   679K|   157   (3)| 00:00:02 |
|   1 |  SORT ORDER BY            |          | 15467 |   679K|   157   (3)| 00:00:02 |
|*  2 |   VIEW                    |          | 15467 |   679K|   155   (2)| 00:00:02 |
|*  3 |    WINDOW SORT PUSHED RANK|          | 15467 |   151K|   155   (2)| 00:00:02 |
|   4 |     TABLE ACCESS FULL     | CUSTOMER | 15467 |   151K|   154   (1)| 00:00:02 |
--------------------------------------------------------------------------------------

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

   2 - filter("RN">=1 AND "RN"<=20)
   3 - filter(ROW_NUMBER() OVER ( ORDER BY "FIRST_NAME")<=20)

我正在使用 Oracle 11g。由于我只查询前 20 行,按索引列排序,因此我希望使用索引。

为什么 Oracle 优化器会忽略索引?我认为分页算法有问题,但我不知道是什么问题。

谢谢。

【问题讨论】:

    标签: sql performance oracle database-performance sql-execution-plan


    【解决方案1】:

    您的 FIRST_NAME 列很可能可以为空。

    SQL> create table customer (first_name varchar2(20), last_name varchar2(20));
    
    Table created.
    
    SQL> insert into customer select dbms_random.string('U', 20), dbms_random.string('U', 20) from dual connect by level <= 100000;
    
    100000 rows created.
    
    SQL> create index c on customer(first_name);
    
    Index created.
    
    SQL> explain plan for select * from (
      2    select /*+ FIRST_ROWS(20) */ FIRST_NAME, ROW_NUMBER() over (order by FIRST_NAME) RN
      3    from CUSTOMER C
      4  )
      5  where RN between 1 and 20
      6  order by RN;
    
    Explained.
    
    SQL> @explain ""
    
    Plan hash value: 1474094583
    
    ----------------------------------------------------------------------------------------------
    | Id  | Operation                 | Name     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT          |          |   117K|  2856K|       |  1592   (1)| 00:00:20 |
    |   1 |  SORT ORDER BY            |          |   117K|  2856K|  4152K|  1592   (1)| 00:00:20 |
    |*  2 |   VIEW                    |          |   117K|  2856K|       |   744   (2)| 00:00:09 |
    |*  3 |    WINDOW SORT PUSHED RANK|          |   117K|  1371K|  2304K|   744   (2)| 00:00:09 |
    |   4 |     TABLE ACCESS FULL     | CUSTOMER |   117K|  1371K|       |   205   (1)| 00:00:03 |
    ----------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - filter("RN">=1 AND "RN"<=20)
       3 - filter(ROW_NUMBER() OVER ( ORDER BY "FIRST_NAME")<=20)
    
    Note
    -----
       - dynamic sampling used for this statement (level=2)
    
    21 rows selected.
    
    SQL> alter table customer modify first_name not null;
    
    Table altered.
    
    SQL> explain plan for select * from (
      2    select /*+ FIRST_ROWS(20) */ FIRST_NAME, ROW_NUMBER() over (order by FIRST_NAME) RN
      3    from CUSTOMER C
      4  )
      5  where RN between 1 and 20
      6  order by RN;
    
    Explained.
    
    SQL> @explain ""
    
    Plan hash value: 1725028138
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation               | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT        |      |   117K|  2856K|       |   850   (1)| 00:00:11 |
    |   1 |  SORT ORDER BY          |      |   117K|  2856K|  4152K|   850   (1)| 00:00:11 |
    |*  2 |   VIEW                  |      |   117K|  2856K|       |     2   (0)| 00:00:01 |
    |*  3 |    WINDOW NOSORT STOPKEY|      |   117K|  1371K|       |     2   (0)| 00:00:01 |
    |   4 |     INDEX FULL SCAN     | C    |   117K|  1371K|       |     2   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - filter("RN">=1 AND "RN"<=20)
       3 - filter(ROW_NUMBER() OVER ( ORDER BY "FIRST_NAME")<=20)
    
    Note
    -----
       - dynamic sampling used for this statement (level=2)
    
    21 rows selected.
    
    SQL>
    

    在那里添加一个 NOT NULL 来解决它。

    SQL> explain plan for select * from (
      2    select /*+ FIRST_ROWS(20) */ FIRST_NAME, ROW_NUMBER() over (order by FIRST_NAME) RN
      3    from CUSTOMER C
      4    where first_name is not null
      5  )
      6  where RN between 1 and 20
      7  order by RN;
    
    Explained.
    
    SQL> @explain ""
    
    Plan hash value: 1725028138
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation               | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT        |      |   117K|  2856K|       |   850   (1)| 00:00:11 |
    |   1 |  SORT ORDER BY          |      |   117K|  2856K|  4152K|   850   (1)| 00:00:11 |
    |*  2 |   VIEW                  |      |   117K|  2856K|       |     2   (0)| 00:00:01 |
    |*  3 |    WINDOW NOSORT STOPKEY|      |   117K|  1371K|       |     2   (0)| 00:00:01 |
    |*  4 |     INDEX FULL SCAN     | C    |   117K|  1371K|       |     2   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - filter("RN">=1 AND "RN"<=20)
       3 - filter(ROW_NUMBER() OVER ( ORDER BY "FIRST_NAME")<=20)
       4 - filter("FIRST_NAME" IS NOT NULL)
    
    Note
    -----
       - dynamic sampling used for this statement (level=2)
    
    22 rows selected.
    
    SQL>
    

    【讨论】:

    • +1 很高兴知道为什么 Oracle 不能不对 nullable 列使用索引。
    • 可以。但前提是索引中至少有一列不可为空。你看,在 oracle 中,NULL 键不在索引中。
    • 例如:SQL&gt; create index c on customer(first_name, id); 如果 ID 是不可为空的列,您也可以获得所需的结果。如果您没有不可为空的列(奇怪),则创建一个函数索引,如create index c on customer(first_name, 0);,文字 0 始终不为空,因此将每个空行强制放入索引。
    【解决方案2】:

    您查询的列多于first_namefirst_name 上的索引仅包含 first_name 列和对表的引用。因此,要检索其他列,Oracle 必须对表本身为每一行执行查找。如果不能保证低记录数,大多数数据库都会尝试避免这种情况。

    数据库通常不够聪明,无法知道where 子句对row_number 列的影响。但是,您的提示 /*+ FIRST_ROWS(20) */ 可能已经成功了。

    也许表真的很小,所以 Oracle 期望表扫描比查找便宜,即使只有 20 行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 2019-06-20
      • 1970-01-01
      • 2014-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多