JDBC查询与JPA查询性能

时间:2020-04-20 16:39:43

标签: java hibernate jpa jdbc

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

这是查询

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 ################
################ getSmartESGScoresPureJDBCWithListCsfLcIds ################
List of Securities CsfLcId 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 Smart Esg Scores 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 Smart Esg Scores records from DB result It took: 00:00:02.553
################ getSmartESGScoresJDBCTemplateWithListCsfLcIds ################
List of Securities CsfLcId retrieved. It took: 00:00:00.087 to execute query on DB using JDBC
Creating 24206 Smart Esg Scores records from DB result It took: 00:00:04.063
Creating 14445 Smart Esg Scores records from DB result It took: 00:00:04.064
################ getSmartESGScoresJPANativeQueryAsESGenius 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 Securities CsfLcId 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 Smart Esg Scores 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 Smart Esg Scores records from DB result It took: 00:00:00.006

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

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

为什么与纯JDBC相比,JPA Native的速度很慢?会是类型转换吗?就我而言,我是在谈论varchar2和数字。我期望得到与JDBC相同的结果。但是从8秒到10分钟就很多了。

我该怎么做才能改善JPA本机查询?

3 个答案:

答案 0 :(得分:3)

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

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

  • 按照文档中的说明为Java应用程序(包括Hibernate和Oracle JDBC驱动程序)启用调试日志记录
  • 观察延迟的来源是数据库,网络还是Java应用程序?如有疑问,请在连接两侧使用Wireshark检查网络流量,或在问题查询之前和之后检查慢速/繁重查询的Oracle数据库统计信息
  • 如果问题是数据库运行缓慢,请确保查询参数的类型与数据库索引匹配
  • 如果您确定网络和数据库没有引起此问题,并且调试日志记录也无法帮助您进一步尝试使用高级工具(例如cpu探查器),例如JVisualVM
  • 如果您仍然遇到问题,则可能是一些极端的内存问题,例如系统内存不足导致交换或非常频繁的Full Garbage Collection(可从Garbage Collection日志记录中看到)

答案 1 :(得分:1)

请注意,如果要比较两个概念,则必须尝试隔离主要功能并摆脱其他因素,这可能会干扰结果。

因此,要查看如果JDBC查询和JPA本机查询的行为不同,我将提出以下方案:

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

  • 使用普通表代替视图

这里有一个简单的设置可以验证性能。该表每个GRP_ID都有50行,因此每1000个键可获得5万行(请参阅下面的脚本来设置表)

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执行+获取已准备好的语句之间没有实质性差异,这可以解释您的观察。两者都在数据库中执行语句执行 ,然后执行许多 fetches -计数取决于使用的 fetch大小

3)当然,视图也可以产生影响,但是在 query 中会有所不同-不在 JDBC v之间。 JPA

4),您没有提及它,因此在此不做详细说明,并假定您的视图不包含任何CLOB列。这当然可以发挥作用。

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

说了警告一句话。

IN列表计数的限制有其用途。对于临时脚本来说,使用大的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 ( ?, ? )

因此,如果出于您的目的使用3万行以上的设备,那么休眠是最好的选择

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 :(得分:0)

JDBC通常比JPA快,但是在JPA中,您可以从缓存中受益,并且这种方式可以获得更好的性能。

我不知道此查询的目的以及如何使用(报告?),但是您应该考虑使用其他条件,而仅列出许多id。我怀疑有些用户手动选择了1000多个ID,因此我想他们是根据其他一些标准选择的。尝试改用这种肌酸。