Oracle根据to_date(literal)的格式使用或忽略索引列

ari*_*lCo 6 oracle indexing to-date

我使用索引列作为过滤器,将其置于'两个文字值之间'.(该列位于索引的第二个位置,实际上使执行速度变慢;我将在稍后处理).

令我困惑的是Oracle(11.2.0.3.0)根据提供给to_date的值的格式和格式字符串使用或忽略所述索引:

这忽略了索引:

SQL> SELECT *
  2  FROM gprs_history_import  gh
  3  WHERE start_call_date_time BETWEEN
  4      to_date('20140610 000000','yyyymmdd hh24miss') AND
  5      to_date('20140610 235959','yyyymmdd hh24miss')
  6  /

Execution Plan
----------------------------------------------------------
Plan hash value: 990804809

--------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                     |   350 |   219K|   242K  (1)| 00:56:42 |       |       |
|   1 |  PARTITION RANGE SINGLE|                     |   350 |   219K|   242K  (1)| 00:56:42 |    74 |    74 |
|   2 |   PARTITION LIST ALL   |                     |   350 |   219K|   242K  (1)| 00:56:42 |     1 |     3 |
|*  3 |    TABLE ACCESS FULL   | GPRS_HISTORY_IMPORT |   350 |   219K|   242K  (1)| 00:56:42 |   220 |   222 |
--------------------------------------------------------------------------------------------------------------

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

   3 - filter("START_CALL_DATE_TIME"<=TO_DATE(' 2014-06-10 23:59:59', 'syyyy-mm-dd hh24:mi:ss'))
Run Code Online (Sandbox Code Playgroud)

这个确实使用了索引(注意第4行中日期部分之后的空格):

SQL> SELECT *
  2  FROM gprs_history_import  gh
  3  WHERE start_call_date_time BETWEEN
  4      to_date('20140610 ','yyyymmdd ') AND
  5      to_date('20140610 235959','yyyymmdd hh24miss')
  6  /

Execution Plan
----------------------------------------------------------
Plan hash value: 464458373

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                          |   350 |   219K|  2795K  (1)| 10:52:15 |       |       |
|*  1 |  FILTER                              |                          |       |       |            |          |       |       |
|   2 |   PARTITION RANGE ITERATOR           |                          |   350 |   219K|  2795K  (1)| 10:52:15 |   KEY |    74 |
|   3 |    PARTITION LIST ALL                |                          |   350 |   219K|  2795K  (1)| 10:52:15 |     1 |     3 |
|   4 |     TABLE ACCESS BY LOCAL INDEX ROWID| GPRS_HISTORY_IMPORT      |   350 |   219K|  2795K  (1)| 10:52:15 |   KEY |   222 |
|*  5 |      INDEX SKIP SCAN                 | GPRS_HISTORY_IMPORT_IDX1 |     1 |       |  2795K  (1)| 10:52:15 |   KEY |   222 |
---------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(TO_DATE('20140610 ','yyyymmdd ')<=TO_DATE(' 2014-06-10 23:59:59', 'syyyy-mm-dd hh24:mi:ss'))
   5 - access("START_CALL_DATE_TIME">=TO_DATE('20140610 ','yyyymmdd ') AND "START_CALL_DATE_TIME"<=TO_DATE(' 2014-06-10
              23:59:59', 'syyyy-mm-dd hh24:mi:ss'))
       filter("START_CALL_DATE_TIME">=TO_DATE('20140610 ','yyyymmdd ') AND "START_CALL_DATE_TIME"<=TO_DATE(' 2014-06-10
              23:59:59', 'syyyy-mm-dd hh24:mi:ss'))
Run Code Online (Sandbox Code Playgroud)

((1)中的过滤器似乎有点傻,好像Oracle 理解表达式)

同样,这个没有(我删除了尾随空格):

SQL> SELECT *
  2  FROM gprs_history_import  gh
  3  WHERE start_call_date_time BETWEEN
  4      to_date('20140610','yyyymmdd') AND
  5      to_date('20140610 235959','yyyymmdd hh24miss')
  6  /

Execution Plan
----------------------------------------------------------
Plan hash value: 990804809

--------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                     |   350 |   219K|   242K  (1)| 00:56:42 |       |       |
|   1 |  PARTITION RANGE SINGLE|                     |   350 |   219K|   242K  (1)| 00:56:42 |    74 |    74 |
|   2 |   PARTITION LIST ALL   |                     |   350 |   219K|   242K  (1)| 00:56:42 |     1 |     3 |
|*  3 |    TABLE ACCESS FULL   | GPRS_HISTORY_IMPORT |   350 |   219K|   242K  (1)| 00:56:42 |   220 |   222 |
--------------------------------------------------------------------------------------------------------------

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

   3 - filter("START_CALL_DATE_TIME"<=TO_DATE(' 2014-06-10 23:59:59', 'syyyy-mm-dd hh24:mi:ss'))
Run Code Online (Sandbox Code Playgroud)

在空间周围加上引号可以排除索引的使用.

是什么赋予了?

Fal*_*lco 2

好的 - 我会尝试一下,这主要是从可用信息中扣除的:

为什么Oracle选择不同的执行计划?

看来在您的第二个具有不寻常日期格式的查询中,优化器不知道结果日期的值是什么。您会看到过滤谓词:

1 - filter(TO_DATE('20140610 ','yyyymmdd ')<=TO_DATE(' 2014-06-10 23:59:59', 'syyyy-mm-dd hh24:mi:ss'))

这意味着优化器甚至不确定第一个日期是否小于第二个日期!这意味着优化器不知道返回的行数,只会使用通用计划,而不考虑特定的统计信息。如果您有一个用户定义的函数 xyt() ,它会返回该范围的日期,情况会是一样的。优化器无法知道将产生什么日期值 - 这意味着您将获得一个通用的万能计划,这对于指定的任何日期范围都应该相当不错。

在第一种和第三种情况下,优化器似乎直接理解日期,并且可以通过使用统计信息猜测日期范围内的行数。因此,虽然第二个查询是针对优化器的,但BETWEEN X AND 3这个查询就像这样BETWEEN 1 AND 3 ,所以他针对预测的返回行数优化了查询计划!

奇怪的事情似乎是,查询优化器有奇怪的日期格式的问题,可以作为错误/改进请求提交......

但重要的一点是:

  1. 全表扫描不一定是一个糟糕的计划...使用索引并不总是更快!
  2. 查询计划中的成本与实际执行时间或性能没有直接关系 - 它是比较同一查询的不同计划的内部测量(因此您不能比较不同查询的成本,例如查询 1,2 和3)

基本上,如果您从表中返回大量行,则在许多情况下,不带索引访问的全表扫描会更快,特别是在某些分区上操作时!- 表扫描将仅访问匹配日期范围的权限 - 因此仅访问相关日期并返回该分区中的所有行。这比查询每行的索引然后通过索引访问提取行要快得多...尝试分析查询 - 分区上的全表扫描速度应该是原来的 3 倍,并且 IO 少得多