Spring Boot JPA+Hibernate 在 SaveAll() 上的低性能
Posted
技术标签:
【中文标题】Spring Boot JPA+Hibernate 在 SaveAll() 上的低性能【英文标题】:Spring Boot JPA+Hibernate low performance on SaveAll() 【发布时间】:2021-03-10 05:31:36 【问题描述】:我正在开发一个 REST 应用程序来加载 CSV 文件,将它们插入 DB(mysql - mysqld Ver 5.7.32),然后以 JSON 格式查询和显示记录。
问题是当我尝试使用 JPA 存储库 SaveAll() 方法保存记录时,它需要很长时间(500 条记录约 25 秒)。
我搜索了解决方案,发现了一些似乎可以解决问题的配置更改,但没有一个对我有用。 我更改了 logging.level.org.hibernate.SQL=DEBUG 和 spring.jpa.properties.hibernate.generate_statistics=true 检查 hibernate 是如何工作的。
没有对应用程序进行任何进一步的更改。500 条记录的属性结果如下:
Id GenerationType.AUTO / ID:14:13:30-14:13:52 / 插入到:14:13:53 / 无批次 / 无 URL / 23 秒
17529760 nanoseconds spent acquiring 501 JDBC connections;
11900589 nanoseconds spent releasing 500 JDBC connections;
201149299 nanoseconds spent preparing 1500 JDBC statements;
946268444 nanoseconds spent executing 1500 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;
511195616 nanoseconds spent executing 1 flushes (flushing a total of 500 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
22s 用于获取和更新 Id。
Hibernate 对传递给 SaveAll() 方法的每个实体执行 3 次查询:
首先它获取所需的 ID:
-
从hibernate_sequence中选择next_val作为id_val进行更新
更新hibernate_sequence set next_val= ? next_val=?
得到Id后:
-
插入表格...
如日志所示,Hibernate 为 500 条记录执行 1500 条语句。
我尝试更改配置如下:
spring.jpa.properties.hibernate.jdbc.batch_size=30
spring.jpa.properties.hibernate.order_inserts=true
得到以下结果:
GenerationType.AUTO / ID:14:23:30-14:23:52 / 插入到:14:23:53 / 批量大小:30,order_inserts=true / 无 URL / 23 秒
15672968 nanoseconds spent acquiring 501 JDBC connections;
13474276 nanoseconds spent releasing 500 JDBC connections;
116274083 nanoseconds spent preparing 1001 JDBC statements;
843429450 nanoseconds spent executing 1000 JDBC statements;
222390695 nanoseconds spent executing 17 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
346810255 nanoseconds spent executing 1 flushes (flushing a total of 500 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
语句减少到 1000 条,已经执行了 17 批,但仍然需要 22 秒来获取 ID 和 1 来执行插入。
我将Id生成策略改为SEQUENCE,结果还是一样的。 另外,我在 MYSQL 连接 URL 中添加了 ?reWriteBatchedInserts=true 以在一个语句中插入多个实体,但休眠仍然会为每条记录生成 1 个插入:
2020-11-27 15:33:15.349 DEBUG 7061 --- [nio-8080-exec-1] org.hibernate.SQL : insert into enra (account_link_code_n, chanel_nam, contract_type_v, cra_ref_num_v, dms_verified_flag_v, id_type_v, msisdn_nsk, profile_type_v, registration_by, registration_date, report_date, shahkar_id, sim_category_code_v, status_code_v, user_code_n, version, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2020-11-27 15:33:15.349 DEBUG 7061 --- [nio-8080-exec-1] org.hibernate.SQL : insert into enra (account_link_code_n, chanel_nam, contract_type_v, cra_ref_num_v, dms_verified_flag_v, id_type_v, msisdn_nsk, profile_type_v, registration_by, registration_date, report_date, shahkar_id, sim_category_code_v, status_code_v, user_code_n, version, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2020-11-27 15:33:15.350 DEBUG 7061 --- [nio-8080-exec-1] org.hibernate.SQL : insert into enra (account_link_code_n, chanel_nam, contract_type_v, cra_ref_num_v, dms_verified_flag_v, id_type_v, msisdn_nsk, profile_type_v, registration_by, registration_date, report_date, shahkar_id, sim_category_code_v, status_code_v, user_code_n, version, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
我有大约 40,000 条记录的 CSV 文件,这在我的应用程序中造成了巨大的瓶颈。
我真的不知道我的配置中缺少什么,但似乎如果我可以让hibernate批量获取和更新Id(hibernate_sequence),时间消耗会大大减少。
非常感谢任何帮助。
【问题讨论】:
据我了解,MySQL 中没有直接的序列支持。这就是为什么有一个表的解决方法,它的性能不是很好。不确定这是否可以通过 Hibernate 批量处理,为什么不使用 MySQL auto_increment? @Thomas 我已尝试使用 @GeneratedValue(strategy = GenerationType.AUTO) 但没有任何变化。 我认为它必须是 @Id @Column(nullable = false, updatable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) - 如果你愿意,请查看 bootify.io。 @Thomas 我使用了 GenerationType.IDENTITY,令人惊讶的是,尽管禁用了批处理,但执行时间却显着提高。现在它在大约 28 秒内保留了 40k 条记录。但随着时间的推移,我的 CSV 文件可能会变大,我认为这可能会导致一级缓存溢出。 【参考方案1】:对于一般观众 - 问题是生成类型。因为 MySQL 不支持序列,所以 hibernate 使用了一个单独的表的解决方法。在每行执行读取和写入之前,性能下降。
解决方案:
@Id
@Column(nullable = false, updatable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
它使用 auto_increment 功能,是 MySQL 的首选选项。 :-)
【讨论】:
以上是关于Spring Boot JPA+Hibernate 在 SaveAll() 上的低性能的主要内容,如果未能解决你的问题,请参考以下文章
制作多个 EntityManager(Spring-Boot-JPA、Hibernate、MSSQL)
Spring Boot / JPA / Hibernate,如何根据 Spring 配置文件切换数据库供应商?
Spring boot之 JPA/Hibernate/Spring Data
Spring Boot JPA Hibernate - 以毫秒精度存储日期
当加载 spring-boot 和 spring-data-jpa 时,Hibernate 无法加载 JPA 2.1 Converter