【问题标题】:JDBC Query vs JPA Query PerformanceJDBC 查询与 JPA 查询性能
【发布时间】:2020-08-03 06:09:39
【问题描述】:

我在从数据库读取数千条记录时遇到了一些与性能相关的问题。我注意到纯 JDBC 查询比 JPA Native 查询快得多。

这里是查询

select ID, COL_A, COL_B, COL_C, COL_D, COL_E, COL_F from MY_SUPER_VIEW_V v 
where 1=1 
and v.ID in (:idList)
and v.DATE_FROM <= :date
and v.DATE_TILL >= :date;

此查询返回大约 38.000 条记录。

idList 中有超过 1000 条记录,因为我使用的是 Oracle DB,所以需要将其拆分为 n 个查询。

此外,我还有一个方法可以将 Object[] 结果转换为我的List&lt;Entity&gt;

为了了解性能问题,我分别创建了一个纯 JDBC 查询和一个 JPA Native 查询来比较结果。

这是时间安排。

################ getScoresPureJDBCWithListIds ################
List of Ids retrieved. It took: 00:00:00.096 to execute query on DB using JDBC
It took: 00:00:01.180 to execute query on DB using JDBC query
Creating 24206 Scores records from DB result It took: 00:00:04.440
It took: 00:00:01.038 to execute query on DB using JDBC query
Creating 14445 Scores records from DB result It took: 00:00:04.307
################ getScoresJPANativeQueryWithListIds ################
It took: 00:06:09.450 to execute query on DB using JPA Native query
Creating 24206 Scores records from DB result It took: 00:00:00.009
It took: 00:04:04.879 to execute query on DB using JPA Native query
Creating 14445 Scores records from DB result It took: 00:00:00.007

使用 Hibernate 分析

################ USING FETCH_SIZE: 2000 ################
################ getScoresPureJDBCWithListIds ################
List of <elements> retrieved. It took: 00:00:00.296 to execute query on DB using JDBC
It took: 00:00:11.940 to execute query on DB using JDBC query
Creating 24206 records from DB result It took: 00:00:02.670
It took: 00:00:13.570 to execute query on DB using JDBC query
Creating 14445 records from DB result It took: 00:00:02.553

################ getScoresJDBCTemplateWithListIds ################
    List of <elements> retrieved. It took: 00:00:00.087 to execute query on DB using JDBC
    Creating 24206 records from DB result It took: 00:00:04.063
    Creating 14445 records from DB result It took: 00:00:04.064
    ################ getScoresJPANativeQueryAsApplication with hint fetch size 2000 ################
    2020-04-22 09:36:30.830  INFO 13262 --- [           main] i.StatisticalLoggingSessionEventListener : Session Metrics {
        1232369 nanoseconds spent acquiring 1 JDBC connections;
        0 nanoseconds spent releasing 0 JDBC connections;
        1448702 nanoseconds spent preparing 1 JDBC statements;
        3992364 nanoseconds spent executing 1 JDBC statements;
        0 nanoseconds spent executing 0 JDBC batches;
        0 nanoseconds spent performing 0 L2C puts;
        0 nanoseconds spent performing 0 L2C hits;
        0 nanoseconds spent performing 0 L2C misses;
        0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
        0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
    }
    List of <ids> retrieved. It took: 00:00:00.261 to execute query on DB using JDBC
    2020-04-22 09:47:23.739  INFO 13262 --- [           main] i.StatisticalLoggingSessionEventListener : Session Metrics {
        73670 nanoseconds spent acquiring 1 JDBC connections;
        0 nanoseconds spent releasing 0 JDBC connections;
        805772 nanoseconds spent preparing 1 JDBC statements;
        651947762290 nanoseconds spent executing 1 JDBC statements; ==> 10 minutes
        0 nanoseconds spent executing 0 JDBC batches;
        0 nanoseconds spent performing 0 L2C puts;
        0 nanoseconds spent performing 0 L2C hits;
        0 nanoseconds spent performing 0 L2C misses;
        0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
        0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
    }
    It took: 00:10:52.898 to execute query on DB using JPA Native query
    Creating 24206 records from DB result It took: 00:00:00.018
    2020-04-22 09:56:00.792  INFO 13262 --- [           main] i.StatisticalLoggingSessionEventListener : Session Metrics {
        2758010 nanoseconds spent acquiring 1 JDBC connections;
        0 nanoseconds spent releasing 0 JDBC connections;
        3096653 nanoseconds spent preparing 1 JDBC statements;
        516148003151 nanoseconds spent executing 1 JDBC statements;
        0 nanoseconds spent executing 0 JDBC batches;
        0 nanoseconds spent performing 0 L2C puts;
        0 nanoseconds spent performing 0 L2C hits;
        0 nanoseconds spent performing 0 L2C misses;
        0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
        0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
    }
    It took: 00:08:37.032 to execute query on DB using JPA Native query
    Creating 14445 records from DB result It took: 00:00:00.006

对于 JDBC 查询,我可以看到 1) 执行查询非常快,但是 2) 在循环中处理每个 ResultSet 元素花费的时间最多为 00:09 秒 int 总

另一方面,对于 JPA Native 查询 1) 通过调用 query.getResultList() 方法执行查询需要大量时间 10:14 秒另一方面 2) 在这里处理每个结果非常快。分析表明,执行 1 条 JDBC 语句需要花费大量时间。即使 FETCH_SIZE = 2000 也没有显着变化。

与纯 JDBC 相比,为什么 JPA Native 很慢? 会不会是类型转换?就我而言,我说的是 varchar2 和数字。我期待与 JDBC 相同的结果。但从 8 秒到 10 分钟的时间很多。

我可以做些什么来改进 JPA Native 查询?

【问题讨论】:

  • v.ID in (:idList) 这很可疑。你在 JDBC 调用中的:idList 中传递了什么?逗号分隔的值列表?
  • 同样首先要验证的是,如果两个方法都使用相同(且适当)的 fetch size 值。
  • 在 JDBC 案例中,我通常设置参数 scorePreparedStatement.setLong(i+3, partition.get(i)) 但在查询中,我必须在 (?, ?, ?, ?, ........, ?)
  • 重读您的问题后,无论是对于 JPA 还是对于 JDBC,这都不可行。 IN 列表旨在解决 少数 个不是 24K 的 ID。 sql_trace JPA 语句,您将看到连接 IN 列表谓词的大量 OR,这显然会使优化器感到困惑。正确的方法是找到一个没有长 IN 列表的解决方案。
  • @ Marmite Bomber inList “just” 有 1600 个元素(这意味着 Oracle 中有 2 个查询,1 个查询 1000,另一个查询 600)。您看到的 24k 是结果中的记录数。

标签: java hibernate jpa jdbc


【解决方案1】:

请注意,如果你想比较两个概念,你必须尝试隔离主要特征并排除其他因素,这可能会干扰结果。

因此,如果要查看 JDBC 查询和 JPA 原生查询的行为是否不同,我会提出以下方案:

  • 仅使用 一个 1000 个元素列表的查询

  • 使用普通表格而不是视图

这里有一个简单的设置来验证性能。该表对于每个 GRP_ID 有 50 行,从而为 1000 个键获得 50K 行(请参阅下面的脚本来设置表)

List params = (13001L..14000L)
def query = session.createNativeQuery("select * from tab where grp_id in (:paramsList) ")
query.setFetchSize(2000)
query.setParameterList("paramsList", params);
result = query.getResultList();

示例运行显示此结果

 got 50000 rows in 1.388 seconds

所以我觉得不需要用普通的 JDBC 重复测试你会看到一个可比较的结果。

更有趣的是重复运行并删除

query.setFetchSize(2000)

这将有效地将获取大小重置为默认值(在我的情况下为 20),相同数据的结果是

 got 50000 rows in 1 minutes, 0.903 seconds

1) 因此,获取大小是对观察到的行为最合理的解释。重要的是检查 JDBC 驱动器是否获得了正确的值并使用它 - 如有疑问,您必须使用 10046 跟踪来查看使用数据库的提取大小。但对我来说,上面的陈述非常有效。

2) 本地 JPA 查询和手动编写的 JDBC 执行 + 获取准备好的语句之间没有实质性区别,这可以解释您的观察。两者都执行在数据库中执行语句,然后执行一些获取 - 计数取决于使用的获取大小

3) 当然 view 也可以产生影响,但它会在 query 中有所不同——而不是在 JDBC v. JPA.

4) 你没有提到它,所以我不在这里详细说明,并假设你的 视图不包含任何 CLOB 列。这当然可以发挥作用。

5) 最后一点是您提到的两个查询 - 您是使用两个独立查询还是一个查询与OR 连接的 IN 列表?您不提供详细信息,因此很难发表评论。无论如何,两个独立的查询应该没有影响。

说了一句警告的话。

IN 列表计数的限制有其目的。 ad Hoc 脚本可以使用大的 IN 列表选择,但对于常规运行的查询,这可能是 解析问题。为什么?

您使用绑定变量能够将以下 quereis 视为单个状态(即仅解析一次)

select * from tab where ID = 1
select * from tab where ID = 2

导致

select * from tab where ID = ?

但以下两个查询(IN 列表的长度不同)仍然不同,必须分别进行额外解析

select * from tab where ID in ( ? )
select * from tab where ID in ( ?, ? )

因此,如果出于您的目的使用 30K 行+,那么 Hibernate 是最佳选择

Hibernate 旨在优雅地满足使用 SQL 的需求,这是大多数开发人员认为的一种酷想法(与大多数 DB 人员相反有相反的含义;)。

这个概念很好用,用例越简单越好。另一方面,批处理有时最好直接使用 SQL

测试数据

create table tab as 
select 
rownum id,
trunc(rownum /  50) +1 grp_id,
rpad('x',100,'y') pad
from dual connect by level <= 1000000;
create index idx on tab(grp_id);

【讨论】:

    【解决方案2】:

    JDBC 通常比 JPA 更快,但在 JPA 中,您可以从缓存中受益,这样可以获得更好的性能。

    我不知道此查询的目的以及如何使用(报告?),但您应该考虑使用不同的标准,然后只列出这么多 id。我怀疑有些用户手动选择了 1000 多个 id,所以我猜他们是通过其他一些标准选择的。尝试改用这个 creatia。

    【讨论】:

    • caching 的参数在这个比较中不太有效,通常如果你使用 JDBC 编程并且你可以完全控制你 不问的查询重复使用数据库中的相同数据 - 缓存闪耀的用例。
    【解决方案3】:

    您似乎比较了两个不同的查询,很可能导致数据库提出不同的查询计划。

    有很多方法可以调查该问题,但我们无法使用这些方法,因为您没有提供最小的可重现示例。因此,我会建议您自己进行一些调查:

    • 为您的 Java 应用程序启用调试日志记录,包括 Hibernate 和 Oracle JDBC 驱动程序,如其文档中所述
    • 观察延迟来自何处,是数据库、网络还是您的 Java 应用程序?如果有疑问,请在连接两侧使用 Wireshark 检查网络流量,或在有问题的查询之前和之后检查 Oracles 数据库关于慢/重查询的统计信息
    • 如果问题是数据库速度慢,请确保查询参数的类型与数据库索引匹配
    • 如果您确定网络和数据库没有导致问题,并且调试日志不能帮助您进一步尝试使用高级工具,例如 cpu 分析器,例如JVisualVM
    • 如果您仍然遇到问题,可能是您遇到了一些极端的内存问题,例如系统内存过少导致交换或非常频繁的完整垃圾收集,您可以从垃圾收集日志中看到这一点

    【讨论】:

      猜你喜欢
      • 2014-05-13
      • 2022-01-03
      • 2011-12-20
      • 2013-04-22
      • 2019-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-16
      相关资源
      最近更新 更多