用于准备语句SLOW的JPA(Hibernate)本机查询

jav*_*ase 7 java sql hibernate jpa prepared-statement

使用JPA背后的Hibernate 3.3.2GA(以及JBoss 5中包含的其余Hibernate包)存在奇怪的性能问题.

我正在使用Native Query,并将SQL组装成预准备语句.

EntityManager em = getEntityManager(MY_DS);
final Query query = em.createNativeQuery(fullSql, entity.getClass());
Run Code Online (Sandbox Code Playgroud)

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

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like ?
Run Code Online (Sandbox Code Playgroud)

并且查询在MSSQL Studio上运行一秒钟.

如果我加

query.setParameter(0, "ABC123%");
Run Code Online (Sandbox Code Playgroud)

查询将暂停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)
Run Code Online (Sandbox Code Playgroud)

但是,如果我只是替换"?" 使用该值(使其不是Prepared Statement,而只是一个直接的SQL查询.

fullSql = fullSql.replace("?", "'ABC123%'");
Run Code Online (Sandbox Code Playgroud)

查询将在不到一秒钟内完成.

我真的更喜欢我们准备好的声明(参数的输入是从用户数据中提取的)以防止注入攻击.

追溯代码中的慢点,我深入到了jtds-1.2.2包中.违规行似乎是SharedSocket line 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.");
    }
Run Code Online (Sandbox Code Playgroud)

通过这个堆栈到达...

  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)
Run Code Online (Sandbox Code Playgroud)

jav*_*ase 9

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

问题在于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
Run Code Online (Sandbox Code Playgroud)

你只需要将sendStringParametersAsUnicode = false传递给你的驱动程序URL设置就可以了.

  • 它需要9秒,因为它不会将您的参数转换为ascii(因为它可能会丢失数据).在与参数进行比较之前,它会将每个列值更改为unicode.这意味着它无法充分利用字段"stringId"上的任何索引,从而导致性能下降得更慢(我相信过去我也遇到过这样的问题). (9认同)

Gar*_*eth 5

检查 SQL Server 正在生成的查询计划。准备好的语句可能特别有问题。

让我解释...

如果你这样做:

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like 'ABC123%';
Run Code Online (Sandbox Code Playgroud)

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

但是,如果您这样做:

SELECT field1, field2, field3 FROM entity left join entity2 on... left join entity3 on
WHERE stringId like ?;
Run Code Online (Sandbox Code Playgroud)

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