在 Oracle XE 11g 中按不使用索引的两列排序

Order by two columns not using index in Oracle XE 11g

为什么这个用于检索前 100 行的简单查询,从给定时间开始,按时间和主键排序(client_time 不是唯一的,这就是按两者排序的原因),不使用索引?

SELECT *
FROM (SELECT *
      FROM requests
      WHERE client_time >= TO_TIMESTAMP('2017-07-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
      ORDER BY client_time ASC, transaction_id ASC
     )
WHERE rownum <= 100;

client_time 是 TIMESTAMP WITH LOCAL TIME ZONE,transaction_id 是 VARCHAR2(255 CHAR)

我希望它使用的索引定义为

CREATE UNIQUE INDEX idx_time_id REQUESTS (client_time, transaction_id);

查询执行大约需要 2 秒(我的系统中有 600 万行,在生产中会更多)并产生以下计划:

----------------------------------------------------------------------------------------------------
| Id  | Operation               | Name             | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                  |   100 |   110K|       | 31237   (1)| 00:06:15 |
|*  1 |  COUNT STOPKEY          |                  |       |       |       |            |          |
|   2 |   VIEW                  |                  |   860K|   931M|       | 31237   (1)| 00:06:15 |
|*  3 |    SORT ORDER BY STOPKEY|                  |   860K|    65M|    86M| 31237   (1)| 00:06:15 |
|*  4 |     TABLE ACCESS FULL   | REQUESTS         |   860K|    65M|       | 15294   (1)| 00:03:04 |
----------------------------------------------------------------------------------------------------

谓词信息(由操作id标识):

   1 - filter(ROWNUM<=100)
   3 - filter(ROWNUM<=100)
   4 - filter("CLIENT_TIME">=TIMESTAMP' 2017-07-01 10:00:00,000000000')

当我删除我的 ORDER BY 子句的第二部分时,实际上使用了这个索引并且在大约 1 毫秒内执行了查询。

如果我 this Use the index, Luke 文章正确,我的查询不应该也使用这个索引吗?

更新:

删除我的第二个订单列后的计划如下所示:

--------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                  |   100 | 65100 |   106   (0)| 00:00:02 |
|*  1 |  COUNT STOPKEY                |                  |       |       |            |          |
|   2 |   VIEW                        |                  |   102 | 66402 |   106   (0)| 00:00:02 |
|   3 |    TABLE ACCESS BY INDEX ROWID| TRX_REQUESTS_LTZ |   102 |  8160 |   106   (0)| 00:00:02 |
|*  4 |     INDEX RANGE SCAN          | IDX_TIME_ID      |       |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

所以我相信 WHERE 子句不是这里的问题。此外,像这样重写 WHERE 后没有任何变化:

WHERE client_time >= TO_TIMESTAMP_TZ('2017-07-01 10:00:00 +10:00', 'YYYY-MM-DD HH24:MI:SS TZH:TZM')

我怀疑原因是 WHERE 子句:

  WHERE client_time >= TO_TIMESTAMP('2017-07-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')

您指定 client_timeTIMESTAMP WITH TIMEZONE。但是,常量只是 TIMESTAMP,没有时区。这意味着需要转换类型——这通常会阻碍索引的使用。

您应该尝试使用 TO_TIMESTAMP_TZ(),记录在案 here

问题是我的索引建立在 VARCHAR2 列上。由于NLS,索引顺序不能直接用来对结果集进行排序。

将 transaction_id 更改为 NUMBER 解决了问题。