使用jdbc时间戳或日期时与Oracle的不可忽略的执行计划差异

Luk*_*der 12 oracle timestamp jdbc sql-execution-plan

我正在分析Oracle执行计划,并发现了一个令人惊讶的事实.看看这个查询.提示只是为了显示我有一个索引,我希望Oracle将它用于范围扫描:

// execute_at is of type DATE.
PreparedStatement stmt = connection.prepareStatement(
    "SELECT /*+ index(my_table my_index) */ * " + 
    "FROM my_table " +
    "WHERE execute_at > ? AND execute_at < ?");
Run Code Online (Sandbox Code Playgroud)

这两个绑定导致完全不同的行为(排除绑定变量偷看问题,我实际上强制执行两个硬解析):

// 1. with timestamps
stmt.setTimestamp(1, start);
stmt.setTimestamp(2, end);

// 2. with dates
stmt.setDate(1, start);
stmt.setDate(2, end);
Run Code Online (Sandbox Code Playgroud)

1)有了时间戳,我得到INDEX FULL SCAN一个过滤谓词

--------------------------------------------------------------
| Id  | Operation                    | Name                  |
--------------------------------------------------------------
|   0 | SELECT STATEMENT             |                       |
|*  1 |  FILTER                      |                       |
|   2 |   TABLE ACCESS BY INDEX ROWID| my_table              |
|*  3 |    INDEX FULL SCAN           | my_index              |
--------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(:1<:2)"
   3 - filter((INTERNAL_FUNCTION(""EXECUTE_AT"")>:1 AND 
               INTERNAL_FUNCTION(""EXECUTE_AT"")<:2))
Run Code Online (Sandbox Code Playgroud)

2)对于日期,我得到了更好的INDEX RANGE SCAN和访问谓词

--------------------------------------------------------------
| Id  | Operation                    | Name                  |
--------------------------------------------------------------
|   0 | SELECT STATEMENT             |                       |
|*  1 |  FILTER                      |                       |
|   2 |   TABLE ACCESS BY INDEX ROWID| my_table              |
|*  3 |    INDEX RANGE SCAN          | my_index              |
--------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(:1<:2)"
   3 - access(""EXECUTE_AT"">:1 AND ""EXECUTE_AT""<:2)
Run Code Online (Sandbox Code Playgroud)

现在我的例子就是一个例子.真正的查询要复杂得多,必须有RANGE SCANSUNIQUE SCANS(取决于谓词)而不是FULL SCANS.

我有什么误解吗?有人能指出我最好的解决方案/做法吗?因为在Java世界中,我认为这java.sql.Timestamp更合适,但我们的大多数列都是Oracle的DATE类型.我们使用的是Java 6和Oracle 11g

APC*_*APC 15

所以问题是,Oracle时间戳和Oracle日期是两种不同的数据类型.为了时间戳比较日期Oracle必须运行转换 - 即INTERNAL_FUNCTION().有趣的设计决策是Oracle转换表列而不是传递的值,这意味着查询不再使用索引.

我已经能够在SQL*Plus中重现您的场景,因此使用它不是问题java.sql.Timestamp.将传递的时间戳转换为日期确实解决了问题...

SQL> explain plan for
  2      select * from test1
  3      where d1 > cast(to_timestamp('01-MAY-2011 00:00:00.000', 'DD-MON-YYYY Hh24:MI:SS.FF') as date)
  4       and d2 > cast(to_timestamp('01-JUN-2011 23:59:59.999', 'DD-MON-YYYY Hh24:MI:SS.FF') as date)
  5  /

Explained.

SQL> select * from table(dbms_xplan.display)
  2  /

PLAN_TABLE_OUTPUT
-----------------------------------------------------------
Plan hash value: 1531258174

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |    25 |   500 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST1 |    25 |   500 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T1_I  |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------    
   2 - access("D1">CAST(TO_TIMESTAMP('01-MAY-2011 00:00:00.000','DD-MON-YYYY
              Hh24:MI:SS.FF') AS date) AND "D2">CAST(TO_TIMESTAMP('01-JUN-2011
              23:59:59.999','DD-MON-YYYY Hh24:MI:SS.FF') AS date) AND "D1" IS NOT NULL)
       filter("D2">CAST(TO_TIMESTAMP('01-JUN-2011 23:59:59.999','DD-MON-YYYY
              Hh24:MI:SS.FF') AS date))

18 rows selected.

SQL>
Run Code Online (Sandbox Code Playgroud)

但我认为这对你没有任何帮助:只是简单地传递日期会更容易.


有趣的是,构建一个基于函数的索引将日期列转换为时间戳并没有帮助.该INTERNAL_FUNCTION()调用未被识别为a CAST()并且忽略该索引.尝试使用INTERNAL_FUNCTION()ORA-00904投掷建立索引.