【问题标题】:Why is Oracle so slow when I pass a java.sql.Timestamp for a DATE column?当我为 DATE 列传递 java.sql.Timestamp 时,为什么 Oracle 这么慢?
【发布时间】:2010-12-29 01:43:03
【问题描述】:

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

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

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

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 上。

【问题讨论】:

  • 你的表分区了吗?当您将参数设置为时间戳时,Oracle JDBC 似乎没有使用分区修剪,原因我一直不明白。
  • +1 我也想了解这一点。这只发生在一张大桌子上吗?
  • 好吧,我猜你不会注意到一张小桌子:)
  • 正如您所指出的,使用 {ts ''} 语法使您的代码与数据库无关,但是有没有办法找出真正传递给数据库的 SQL 是什么?与数据库无关有多重要?如果您可以发布 EXPLAIN PLAN 结果,我们或许能够了解更多正在发生的事情。
  • Bob:解释计划显示完全相同的结果,即使我使用 TO_DATE()。

标签: performance oracle date jdbc timestamp


【解决方案1】:

我这里也有类似的问题:

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 中:

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 有道理;优化器可能太“愚蠢”,无法理解在这种情况下向下转换类型会更有效。
    【解决方案3】:

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

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

    您通过以下方式设置连接属性 将其添加到 java.util.Properties 对象传递给 DriverManager.getConnection 或 OracleDataSource.setConnectionProperties。 您通过以下方式设置系统属性 在你的 java 中包含一个 -D 选项 命令行。

    java -Doracle.jdbc.V8Compatible="true" 我的应用程序

    注意 11g,这个属性显然没有被使用。

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

    对于那些 使用 11gR1(及更高版本)JDBC 瘦 驱动程序:V8兼容连接 财产不再存在,所以你不能 依靠它来发送您的 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
    

    【讨论】:

      【解决方案4】:

      我不明白 {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')
      

      【讨论】:

      • +1 这个查询的语法看起来更简单,更容易理解。我们使用这样的东西,没有问题。
      • {ts ...}, {d ...} 是 JDBC 的一个隐藏功能,它允许您以与 DB 无关的方式指定 java.sql.Timestamp、java.sql.Date。
      • 至于您的查询,我的问题更多:如何在不查看所有来源的情况下避免这种情况?
      • 隐藏特性...我开始明白了 ;-) 所以你真的不知道 Oracle 收到的确切 SQL 是什么! ;-) 要找出答案,也许您可​​以通过比较几个查询的性能直接在 Oracle 中重现问题,只改变使用的日期格式(在这个答案中)?但不要忘记让我们更新! :-)
      • 我很确定问题是隐式类型转换之一。如果您给 Oracle 一个时间戳值以与 DATE 类型列进行比较,那么它将必须进行转换,并且它将避免四舍五入。因此,唯一的选择是将其所有 DATE 值转换为 TIMESTAMP。因此它会非常慢,并且无法在 DATE col 上使用索引。您可以通过在日期 col 上创建一个基于函数的索引来解决这个问题,其中日期表示为时间戳。
      猜你喜欢
      • 2020-03-19
      • 1970-01-01
      • 2019-06-28
      • 2012-12-26
      • 1970-01-01
      • 2022-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多