【问题标题】:Hibernate criteria takes too long in web app but it is fast in SQLPlus?Hibernate 标准在 Web 应用程序中花费的时间太长,但在 SQLPlus 中却很快?
【发布时间】:2013-10-17 02:04:19
【问题描述】:

在 web 应用程序中,休眠条件对 oracle db 花费的时间太长。我启用 log4j.logger.org.hibernate.SQL=debug SQL 并在 sql 中使用相同的绑定变量运行 sql 查询,结果是实例。启用休眠日志并浏览日志。是什么导致hibernate花费太长时间摩擦查询?有什么建议么?

更新 1:

当我通过 SQLPlus 运行 hibernate 生成的相同查询时,oracle 似乎使用不同的执行计划。 来自 Hibernate 的 SQL 查询:

select count(*) as y0_ from SUMMARY_VIEW this_ where this_.ser_id like :1 and this_.TYPE=:2 and this_.TIME_LOCAL>=:3 and this_.TIME_LOCAL<=:4 

在 SQLPlus 上运行 SQL 查询:

select count(*) as y0_ from SUMMARY_VIEW this_ where this_.ser_id like :ser_id and this_.TYPE=:type and this_.TIME_LOCAL>=:startdate and this_.TIME_LOCAL<=:enddate

更新 2: 进一步调查发现 startdate 和 endate 绑定变量作为 varchar2 从 sqlplus 传递,但这些作为时间戳从 app (:) 传递。由于这个执行计划不同。

select sql_text, v.sql_id, name, value_string, datatype_string from v$sql_bind_capture vbc  join v$sql v using (hash_value) where v.sql_id in (?)

绑定变量类型会影响执行计划吗?如果是这样,是否有任何其他工具可以将日期变量作为绑定参数传递给查询?

更新 3: 由于数据类型不兼容导致的性能问题。看来列数据类型(DATE)和休眠数据类型(TIMESTAMP)不匹配会导致隐式数据类型转换。 Oracle 使用 INTERNAL_FUNCTION 来传输日期列以匹配传递的绑定变量休眠数据类型 TimeStamp。

类似问题:

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

Why is Oracle so slow when I pass a java.sql.Timestamp for a DATE column?

【问题讨论】:

  • 也许需要很长时间的是对象树的映射。您是否尝试过原生查询(返回 Object[])来查看性能是否有所提高?
  • prod 中出现问题。现在无法验证这一点,模型类中的字段是原始数据类型和字符串。所以我相信它不会构造对象树。
  • 好吧,抱歉,如果没有更多信息,我们无法为您提供帮助。继续并分析您的代码/环境。当您有更多信息时,如果您仍然需要帮助,请回来更新问题,我一定会看看(可能是几件事:您可能正在耗尽您的连接池/使用错误的事务范围,这可能会使您的查询等待/锁定/有环境问题/等等等等,没有办法通过猜测来帮助)。
  • 当您在 SQLPlus 中“使用相同的绑定变量”时,您是否真的在 SQLPlus 中使用了绑定变量(例如 :b1、:b2),或者您是否将值填写到 SQL 语句中你可以在 SQLPlus 中吗?
  • 同一个查询在不同会话中的运行可能不同的原因有很多。首先,您应该可以使用select * from gv$sql;select * from table(dbms_xplan.display_cursor(sql_id =&gt; '[sql id]', cursor_child_no =&gt; '[cursor child no]')); 之类的方式找到查询及其计划。如果只有一个子游标,则数据库正在执行相同的查询,这是客户端问题。如果有计划差异,那么您可以查看不同的会话设置,例如 NLS 参数,或者可能为绑定变量使用了不同的值?

标签: java sql oracle hibernate sql-execution-plan


【解决方案1】:

如果您使用的是 Hibernate,则可以使用此处指示的解决方案来解决此问题:

http://blog.jooq.org/2014/12/29/leaky-abstractions-or-how-to-bind-oracle-date-correctly-with-hibernate/

基本上,您需要使用UserType

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Objects;

import oracle.sql.DATE;

import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

public class OracleDate implements UserType {

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.TIMESTAMP };
    }

    @Override
    public Class<?> returnedClass() {
        return Timestamp.class;
    }

    @Override
    public Object nullSafeGet(
        ResultSet rs, 
        String[] names, 
        SessionImplementor session, 
        Object owner
    )
    throws SQLException {
        return rs.getTimestamp(names[0]);
    }

    @Override
    public void nullSafeSet(
        PreparedStatement st, 
        Object value, 
        int index, 
        SessionImplementor session
    )
    throws SQLException {
        // The magic is here: oracle.sql.DATE!
        st.setObject(index, new DATE(value));
    }

    // The other method implementations are omitted
}

并使用该类型注释所有实体:

@Entity
@TypeDefs(
    value = @TypeDef(
        name = "oracle_date", 
        typeClass = OracleDate.class
    )
)
public class Rental {

    @Id
    @Column(name = "rental_id")
    public Long rentalId;

    @Column(name = "rental_date")
    @Type(type = "oracle_date")
    public Timestamp rentalDate;
}

恐怕有很多重复的样板,但至少你可以让执行计划再次加快速度。

JDBC 和其他 API

为了记录,一篇类似的文章介绍了如何在 JDBC 层解决这个问题,或者特别是使用 jOOQ:

http://blog.jooq.org/2014/12/22/are-you-binding-your-oracle-dates-correctly-i-bet-you-arent/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多