Spring数据保存与saveAll性能

Posted

技术标签:

【中文标题】Spring数据保存与saveAll性能【英文标题】:Spring data save vs saveAll performance 【发布时间】:2018-09-26 21:35:21 【问题描述】:

我试图理解为什么 saveAll 比保存在 Spring Data 存储库中的性能更好。我正在使用CrudRepository,可以看到here。

为了测试,我创建了 10k 个实体并将它们添加到一个列表中,这些实体只有一个 id 和一个随机字符串(对于基准测试,我将字符串保持为常数)。遍历我的列表并在每个元素上调用.save,花了 40 秒。在同一整个列表上调用 .saveAll 在 2 秒内完成。使用 30k 个元素调用 .saveAll 需要 4 秒。在执行每个测试之前,我确保截断我的表。即使将 .saveAll 调用批处理到 50 个子列表也需要 10 秒和 30k。

带有整个列表的简单.saveAll似乎是最快的。

我尝试浏览 Spring Data 源代码,但 this 是我发现的唯一有价值的东西。在这里,.saveAll 似乎只是迭代整个Iterable 并像我一样在每个.save 上调用。那么它是如何快得多的呢?它是否在内部进行一些事务批处理?

【问题讨论】:

Spring是开源的,你看代码了吗? 你的交易边界在哪里?显示您的测试代码会有所帮助。 【参考方案1】:

如果没有您的代码,我不得不猜测,我认为这与在 save 的情况下为保存的每个对象创建新事务的开销有关,而在 saveAll 的情况下打开一个事务。

注意savesaveAll 的定义,它们都用@Transactional 注释。如果您的项目配置正确,这似乎是因为实体被保存到数据库中,这意味着每当调用这些方法之一时都会创建一个事务。如果您在循环中调用save,这意味着每次调用save 时都会创建一个新事务,但在saveAll 的情况下,无论有多少实体,都会创建一个调用保存。

我假设测试本身并没有在事务中运行,如果要在事务中运行,那么所有对保存的调用都将在该事务中运行,因为默认的事务传播是Propagation.REQUIRED,即意味着如果有一个事务已经打开,调用将在其中运行。如果您打算使用 Spring Data,我强烈建议您阅读 transaction management in Spring。

【讨论】:

这并不完全准确。 @Transactional 默认传播级别是Required,这意味着在实际调用此方法之前应该存在事务。由于传播级别,CrudRepository 不会自行创建事务。 @edward_wong 实际创建交易的人与他的问题无关,我没有说创建交易的是 CrudRepository。仍然提到默认传播很重要,因为与始终需要创建新事务的 Propagation.REQUIRE_NEW 不同,Propagation.REQUIRED 将继续使用在外部范围内创建的事务(以及与他的问题无关的其他语义),这解释了直接在他自己的循环中调用 save() 或调用 saveAll() 之间的性能差异。 @edward_wong "需要传播:支持当前事务,如果不存在则创建一个新事务。类似于同名的 EJB 事务属性。"所以它会默认创建新的。 docs.spring.io/spring-framework/docs/current/javadoc-api/org/…

以上是关于Spring数据保存与saveAll性能的主要内容,如果未能解决你的问题,请参考以下文章

Spring Batch/Data JPA 应用程序在调用 JPA 存储库(save、saveAll)方法时不会将数据持久化/保存到 Postgres 数据库

Spring数据JPA存储库saveAll不生成批量插入查询

cakephp:使用 saveAll(),导入的(非表单相关的)关联数据不保存

如何使用 Spring Boot 在 Mongodb 中保存重复项?

如何使 saveAll 结果返回可分页?

SaveAll 不保存关联的 hasOne 数据