【问题标题】:JPA (Hibernate) Native Query for Prepared Statement SLOW准备好的语句的 JPA (Hibernate) 本机查询慢
【发布时间】:2012-01-20 22:53:06
【问题描述】:

在 JPA 之后使用 Hibernate 3.3.2GA(以及 JBoss 5 中包含的其余 Hibernate 软件包)遇到奇怪的性能问题。

我正在使用 Native Query,并将 SQL 组装成一个准备好的语句。

EntityManager em = getEntityManager(MY_DS);
final Query query = em.createNativeQuery(fullSql, entity.getClass());

SQL 有很多连接,但实际上非常基本,只有一个参数。喜欢:

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like ?

查询在 MSSQL Studio 上运行不到一秒钟。

如果我添加

query.setParameter(0, "ABC123%");

查询将暂停 9 秒

2012-01-20 14:36:21 - TRACE: - AbstractBatcher.getPreparedStatement:(484) | preparing statement
2012-01-20 14:36:21 - TRACE: - StringType.nullSafeSet:(133) | binding 'ABC123%' to parameter: 1
2012-01-20 14:36:30 - DEBUG: - AbstractBatcher.logOpenResults:(382) | about to open ResultSet (open ResultSets: 0, globally: 0)

但是,如果我只是替换“?”使用值(使其不是准备好的语句,而只是一个直接的 SQL 查询。

fullSql = fullSql.replace("?", "'ABC123%'");

查询将在不到一秒的时间内完成。

我真的希望我们使用 Prepared Statement(参数的输入是从用户数据中提取的)来防止注入攻击。

追踪代码中的慢点,我深入到了 jtds-1.2.2 包中。违规行似乎是 SharedSocket 第 841 行“getIn().readFully(hdrBuf);”不过那里没有什么明显的......

private byte[] readPacket(byte buffer[])
        throws IOException {
    //
    // Read rest of header
    try {
        getIn().readFully(hdrBuf);
    } catch (EOFException e) {
        throw new IOException("DB server closed connection.");
    }

通过这个堆栈到达...

  at net.sourceforge.jtds.jdbc.SharedSocket.readPacket(SharedSocket.java:841)
  at net.sourceforge.jtds.jdbc.SharedSocket.getNetPacket(SharedSocket.java:722)
  at net.sourceforge.jtds.jdbc.ResponseStream.getPacket(ResponseStream.java:466)
  at net.sourceforge.jtds.jdbc.ResponseStream.read(ResponseStream.java:103)
  at net.sourceforge.jtds.jdbc.ResponseStream.peek(ResponseStream.java:88)
  at net.sourceforge.jtds.jdbc.TdsCore.wait(TdsCore.java:3928)
  at net.sourceforge.jtds.jdbc.TdsCore.executeSQL(TdsCore.java:1045)
  at net.sourceforge.jtds.jdbc.TdsCore.microsoftPrepare(TdsCore.java:1178)
  at net.sourceforge.jtds.jdbc.ConnectionJDBC2.prepareSQL(ConnectionJDBC2.java:657)
  at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.executeQuery(JtdsPreparedStatement.java:776)
  at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)
  at org.hibernate.loader.Loader.getResultSet(Loader.java:1808)
  at org.hibernate.loader.Loader.doQuery(Loader.java:697)
  at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
  at org.hibernate.loader.Loader.doList(Loader.java:2228)
  at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2125)
  at org.hibernate.loader.Loader.list(Loader.java:2120)
  at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:312)
  at org.hibernate.impl.SessionImpl.listCustomQuery(SessionImpl.java:1722)
  at org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:165)
  at org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:175)
  at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:67)

【问题讨论】:

  • 让我将 jtds-1.2.2 添加到技术堆栈中。我已经通过 Hibernate 调试到 JTDS
  • 试过jtds-1.2.4,但不喜...
  • 切换到 com.microsoft.sqlserver.jdbc.SQLServerDriver 实际上会产生相同的结果......必须看看 SQL Server,也许那里有什么......

标签: java sql hibernate jpa prepared-statement


【解决方案1】:

我会留下这个问题并在这里回答,以防将来有人遇到同样的问题。

问题在于 JTDS 驱动程序将参数字符串发送到 MSSQL 的方式。显然 Java 默认会尝试发送参数 Unicode,MSSQL 会将其转换为 Ascii。为什么需要 9 秒,我不知道。

那里有很多对此的引用,但在我能够确定这是驱动程序与 MSSQL 连接的问题之前,没有任何帮助。

此链接很有帮助:

[http://server.pramati.com/blog/2010/06/02/perfissues-jdbcdrivers-mssqlserver/]

这是使用 Microsoft 驱动程序的字符串。

jdbc:sqlserver://localhost\SQLEXPRESS;
  DatabaseName=TESTDB;
  sendStringParametersAsUnicode=false

您只需将 sendStringParametersAsUnicode=false 传递给您的驱动程序 URL 设置即可。

【讨论】:

  • 这需要 9 秒,因为它不会将您的参数转换为 ascii(因为这样可能会丢失数据)。在与您的参数进行比较之前,它将每列值更改为 unicode。这意味着它不能充分利用“stringId”字段上的任何索引,从而导致性能下降(我相信我过去也遇到过这样的问题)。
  • 顺便说一句,我很高兴你解决了你的问题。
  • 感谢您提供此信息,我认为是休眠导致了问题
【解决方案2】:

检查 SQL Server 生成的查询计划。准备好的语句可能尤其成问题。

让我解释一下……

如果你这样做:

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like 'ABC123%';

并且您在“stringId”上有一个索引,SQL 服务器知道它可以使用它。

但是,如果你这样做:

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like ?;

SQL 服务器在创建准备好的语句时不知道它可以使用索引(因为您可以使用 '%ABC123' 而不是 'ABC123%' 填写参数),因此可能会选择完全不同的查询计划。

【讨论】:

  • 谢谢加雷斯。你说的是100%真实的。在这种情况下,主要问题在于驱动程序-MSSQL 连接。我认为MSSQL可能会缓存查询计划,实际上SP的可能迭代只有几个,一对多关系有不同的子查询。修复该问题后,100 个查询在 47 秒内返回,每个查询都超过 9 秒!
  • 有解决办法吗?
【解决方案3】:

对于可能使用 Oracle 并遇到类似 Unicode 问题的人的另一个答案...

检查以确保没有人设置属性 oracle.jdbc.defaultNChar=true

有时这样做是为了解决 unicode 问题,但这意味着所有列都被视为 nvarchars。如果您在 varchar 列上有索引,则不会使用它,因为 oracle 必须使用函数来转换字符编码。

【讨论】:

    猜你喜欢
    • 2020-05-20
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多