【发布时间】:2021-01-04 05:13:17
【问题描述】:
当我尝试更新测试数据(1,000 个实体)时,花了 1m 5s。
所以我阅读了很多文章,然后将处理时间缩短到 20 秒。
但这对我来说仍然很慢,我相信有比我使用的方法更多的好解决方案。有没有人有很好的做法来处理这个问题?
我也想知道是哪一部分让它变慢了?
- 持久性上下文
- 其他选择
谢谢!
@Entity 类
该实体类是从用户手机中收集到用户步行步数的健康数据。
PK是userId和recorded_at(PK的recorded_at来自请求数据)
@Getter
@NoArgsConstructor
@IdClass(StepId.class)
@Entity
public class StepRecord {
@Id
@ManyToOne(targetEntity = User.class, fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
@Id
private ZonedDateTime recordedAt;
@Column
private Long count;
@Builder
public StepRecord(User user, ZonedDateTime recordedAt, Long count) {
this.user = user;
this.recordedAt = recordedAt;
this.count = count;
}
}
Id 类
Id class(here) 中的用户字段,它是 UUID 类型。 In Entity class,用户是用户实体类型。没问题,会不会有问题?
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class StepId implements Serializable {
@Type(type = "uuid-char")
private UUID user;
private ZonedDateTime recordedAt;
}
请求数据示例
// I'll get user_id from logined user
// user_id(UUID) like 'a167d363-bfa4-48ae-8d7b-2f6fc84337f0'
[{
"count": 356,
"recorded_at": "2020-09-16T04:02:34.822Z"
},
{
"count": 3912,
"recorded_at": "2020-09-16T08:02:34.822Z"
},
{
"count": 8912,
"recorded_at": "2020-09-16T11:02:34.822Z"
},
{
"count": 9004,
"recorded_at": "2020-09-16T11:02:34.822Z" // <-- if duplicated, update
}
]
数据库数据示例
|user_id (same user here) |recorded_at |count|
|------------------------------------|-------------------|-----|
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 04:02:34|356 | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 08:21:34|3912 | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 11:02:34|9004 | <-update
解决方案 1:SaveAll() 与批处理
- application.properties
spring:
jpa:
properties:
hibernate:
jdbc.batch_size: 20
jdbc.batch_versioned_data: true
order_inserts: true
order_updates: true
generate_statistics: true
- 服务
public void saveBatch(User user, List<StepRecordDto.SaveRequest> requestList) {
List<StepRecord> chunk = new ArrayList<>();
for (int i = 0; i < requestList.size(); i++) {
chunk.add(requestList.get(i).toEntity(user));
if ( ((i + 1) % BATCH_SIZE) == 0 && i > 0) {
repository.saveAll(chunk);
chunk.clear();
//entityManager.flush(); // doesn't help
//entityManager.clear(); // doesn't help
}
}
if (chunk.size() > 0) {
repository.saveAll(chunk);
chunk.clear();
}
}
我读过文章说如果我在 Entity 类中添加“@Version”字段,但它仍然是额外的选择。并且花费了几乎相同的时间(20 秒)。
但这对我没有帮助。我想我将 PK 密钥与数据一起传递,所以它总是调用 merge()。
(如果我对@Version 有误解,请告诉我)
解决方案2:Mysql Native Query (insert into~ on duplicate key update~)
我猜Insert into ~ on duplicate key update ~在mysql原生查询中可能比merge() <- select/insert快
mysql原生查询也可以选择检查重复键,但我猜mysql引擎优化得很好。
- 存储库
public interface StepRecordRepository extends JpaRepository<StepRecord, Long> {
@Query(value = "insert into step_record(user_id, recorded_at, count) values (:user_id, :recorded_at, :count) on duplicate key update count = :count", nativeQuery = true)
void upsertNative(@Param("user_id") String userId, @Param("recorded_at") ZonedDateTime recorded_at, @Param("count") Long count);
}
- 服务
public void saveNative(User user, List<StepRecordDto.SaveRequest> requestList) {
requestList.forEach(x ->
repository.upsertNative(user.getId().toString(), x.getRecordedAt(), x.getCount()));
}
对于 1000 个实体,这两种方法都需要 20 秒。
【问题讨论】:
-
您是否尝试为您的实体覆盖 equals() 和 hashCode()?
-
也许这个链接对你有用:stackoverflow.com/questions/14936266/…
-
@Zogger No. 为什么需要equals()和hashCode()?
-
我读了你的链接。我已经尝试过
entityManager.flush()和entityManager.clear(),但它没有帮助:(。仍然是 20 秒 -
数据库和环境在同一个网络吧?
标签: spring jpa batch-processing bulkinsert