JDBC查询与JPA查询性能

uyto3xhc  于 2023-10-19  发布在  其他
关注(0)|答案(3)|浏览(133)

我在从数据库中阅读数千条记录时遇到了一些与性能相关的问题。我注意到纯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<Entity>
为了理解性能问题,我分别创建了一个纯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 seconds int total
另一方面,对于JPA Native查询1)通过调用query.getResultList()方法执行查询需要花费大量时间10:14秒2)处理每个结果在这里相当快。分析表明,执行一条JDBC语句花费了大量的时间。即使使用FETCH_SIZE = 2000,也没有显著变化。

**为什么JPA Native与纯JDBC相比非常慢?**是否是类型转换?在我的例子中,我谈论的是varchar2和数字。我希望得到与JDBC相同的结果。但从8秒到10分钟,这是很多。
我可以做些什么来改进JPA Native查询?

huwehgph

huwehgph1#

您似乎在比较两个不同的查询,很可能导致数据库产生不同的查询计划。
有很多方法可以研究这个问题,但没有一种方法对我们可用,因为你没有提供一个最小的可重复的例子。因此,我会建议你自己调查这个问题的一些选择:

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

mm5n2pyu2#

请注意,如果你想比较两个概念,你必须尝试隔离主要特征,并摆脱其他因素,这可能会干扰结果。
因此,为了看看JDBC查询和JPA原生查询在行为上是否有差异,我提出了以下场景:

  • 只对1000个元素列表使用 * 一个 * 查询
  • 使用 plain table 而不是view

这里有一个简单的设置来验证性能。表中每个GRP_ID有50行,因此1000个键有50 K行(请参阅下面的脚本来设置表)

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 execute + fetch预准备语句之间没有实质性差异,这可以解释您的观察结果。两者都执行 * 在数据库中执行语句 *,然后执行多次 * 获取 * -计数取决于使用的 * 获取大小 *
**3)**当然,view 也会有影响,但这将是 query 中的差异-而不是 JDBC与 * jdbc之间的差异。JPA
**4)**您没有提到它,所以我在这里不详细介绍,并假设您的视图不包含任何CLOB列。这当然可以发挥作用。
**5)**最后一点是在你提到的 * 两个查询 * -你使用两个独立的查询或一个查询whith OR串联在一起的IN列表?你没有提供细节,所以很难评论。无论如何,两个独立的查询应该没有影响。

说了一句警告的话。
IN列表计数的限制有其目的。对于 * ad Hoc脚本 * 来说,使用大的IN列表选择是可以接受的,但是对于常规运行的查询,这可能是一个 * 解析问题 *。为什么?为什么?
使用绑定变量可以将以下查询视为单个语句(仅解析一次)

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 ( ?, ? )

因此,重新考虑一下,对于您的目的,30 K行+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);
abithluo

abithluo3#

JDBC通常比JPA快,但在JPA中,您可以从缓存中受益,从而获得更好的性能。
我不知道这个查询的目的和如何使用(报告?),但你应该考虑使用不同的标准,然后只是列出这么多的id。我怀疑有些用户已经选择了1000+身份证手动,所以我猜他们是选择了一些其他标准批。尝试使用此创意。

相关问题