当我为DATE列传递java.sql.Timestamp时,为什么Oracle这么慢?

时间:2009-12-22 10:51:38

标签: performance oracle date jdbc timestamp

我有一个带有DATE列的时间表(与Oracle中一样,因为没有TIME类型)。当我从JDBC查询该列时,我有两个选项:

  • 使用Oracle的to_date()
  • 手动转换值
  • 使用java.sql.Timestamp

这两种方法都有效,并且具有独特的可怕区域。我的问题是当我SELECT数据时。以下是两个示例查询:

select *
from TABLE
where TS between {ts '2009-12-08 00:00:00.000'} and {ts '2009-12-09 00:00:00.000'}

select *
from TABLE
where TS between trunc({ts '2009-12-08 00:00:00.000'}) and trunc({ts '2009-12-09 00:00:00.000'})

两个查询都有效,返回相同的结果并在EXPLAIN PLAN中生成完全相同的输出。使用了这个正确的索引。

只有查询一个运行15分钟,而第二个查询需要0.031秒。这是为什么?是否有一个中心位置来解决这个问题,或者我是否必须检查此列的所有查询并确保trunc()在那里?当我需要选择一定时间时,如何解决此问题?

[编辑]该表已分区,我在Oracle 10.2.0上。

4 个答案:

答案 0 :(得分:4)

这是因为TIMESTAMP数据类型比DATE更准确,因此当您将TIMESTAMP参数值提供到DATE列条件时,Oracle必须将所有DATE值转换为TIMESTAMP以进行比较(这是上面的INTERNAL_FUNCTION用法),因此索引具有完全扫描。

答案 1 :(得分:3)

我不明白{ts'2009-12-08 00:00:00.000'}实际意味着什么,因为据我所知,这不是Oracle SQL。你能准确显示你正在运行的查询吗?

一个可能的问题是你用毫秒指定你的范围。 Oracle的DATE类型只有几秒钟。 (如果需要存储几分之一秒,请使用TIMESTAMP类型)。但可能发生的是,在第一个查询中,Oracle将每个DATE值转换为TIMESTAMP,以便与指定的TIMESTAMP进行比较。 在第二种情况下,它知道TRUNC()会将您的值有效地舍入到可以表示为DATE的值,因此不需要转换。

如果你想避免这种隐含的转换,请确保你总是比较喜欢。 例如

select * 
from my_table t
where t.ts between to_date('2009-12-08','YYYY-MM-DD') and to_date('2009-12-09','YYYY-MM-DD')

答案 2 :(得分:3)

我在这里有类似的问题:

Non-negligible execution plan difference with Oracle when using jdbc Timestamp or Date

在我的示例中,它基本上归结为使用JDBC时间戳时,INTERNAL_FUNCTION应用于过滤器列,而不是绑定变量。因此,索引不能再用于RANGE SCANSUNIQUE SCANS

// 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 < ?");

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

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

// 2. with dates
stmt.setDate(1, start);
stmt.setDate(2, end);

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))

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)

在第三方API中解决此问题

为了记录,这个问题也可以在第三方API中解决,例如在Hibernate中:

或者在jOOQ:

答案 3 :(得分:2)

前一段时间我在一个项目上遇到这个问题,设置连接属性oracle.jdbc.V8Compatible = true修复了问题。

Dougman的链接告诉你如何设置它:

  

您可以设置连接属性   将它添加到java.util.Properties   对象传递给   DriverManager.getConnection或   OracleDataSource.setConnectionProperties。   您可以设置系统属性   在java中包含-D选项   命令行。

     

java -Doracle.jdbc.V8Compatible =“true”   MyApp的

注意11g并且显然没有使用此属性。

来自http://forums.oracle.com/forums/thread.jspa?messageID=1659839

  

对于那些人来说,还有一个额外的注释   使用11gR1(和on)JDBC瘦   driver:V8Compatible连接   财产不再存在,所以你不能   靠那个发送你的   java.sql.Timestamp作为SQLDATE。什么   你可以做的就是打电话:

setObject(i, aTimestamp, java.sql.Types.DATE) sends data as SQLDATE
setObject(i, aDate) sends data as SQLDATE
setDate(i, aDate) sends data as SQLDATE
setDATE(i, aDATE) (non standard) sends data as SQLDATE

setObject(i, aTimestamp) sends data as SQLTIMESTAMP
setTimestamp(i, aTimestamp) sends data as SQLTIMESTAMP
setObject(i, aTimestamp) sends data as SQLTIMESTAMP
setTIMESTAMP(i, aTIMESTAMP) (non standard) sends data as SQLTIMESTAMP