我有一个带有DATE
列的时间表(与Oracle中一样,因为没有TIME
类型)。当我从JDBC查询该列时,我有两个选项:
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上。
答案 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 SCANS
或UNIQUE 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中解决,例如在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