ScrollableResultSet.next() 在滚动时使用 Session.save() 时逐渐变慢
ScrollableResultSet.next() gradually slows down when using Session.save() whilst scrolling
我正在使用 ScrollableResults
对象从 table 滚动浏览大约 500,000 到 1,000,000 行。在滚动时,我使用每次迭代生成的实体创建一个不同的实体,并使用 session.save()
来保留该对象。下面是示例代码,其中实际代码更复杂但本质上做同样的事情。
Session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
ScrollableResults results = session.createQuery("from Foo_Table f join f.bars b")
.scroll(ScrollMode.FORWARD_ONLY);
int i = 0;
while(results.next())
{
Foo foo = (Foo) results.get(0);
Bar bar = new Baz(foo);
bar.setFoo(foo);
session.save(bar)
if(i % 50 == 0)
{
session.flush();
session.clear();
}
}
tx.commit();
session.close();
重要实体:
@Entity
@Table(name = "FOO_TABLE")
public class Foo_Entity implements Serializable {
@Id
@Column(name = "Foo_ID", nullable=false)
private String id;
@OneToMany(fetch = FetchType.EAGER, //FetchType.LAZY fixes the slow down
mappedBy = "fooParent", cascade = CascadeType.ALL)
private Set<Bar> bar_entities = new HashSet<>(0);
}
@Entity
@Table(name = "BAR_TABLE")
public class Bar_Entity implements Serializable {
@Id
@GeneratedValue
@Column(name="Id")
private Long id;
@ManyToOne
@JoinColumn(name="foo_pk")
private Foo fooParent;
// setFoo, getFoo...
}
当我为这个事务计时时,运行 时间从每 500 次迭代大约 100 毫秒开始,但在大约 20,000 次迭代后逐渐增加到每 500 次迭代几秒。结果,该事务的性能极差。唯一需要时间的代码行是 results.next()
,它的执行时间越来越长。
如果我将 Foo 中 Bar 实体的获取类型从 eager 更改为 lazy,问题就解决了。我不明白为什么对尚未填充的集合使用急切获取类型会导致滚动浏览包含该关系的实体时出现问题。在 session.flush() 上滚动期间确实填充了集合,但在我的场景中,集合通常只填充了一到两个元素,这就是为什么我更愿意将此获取类型设置为 eager.
有谁知道为什么在这种特定情况下会出现这种减速?
请注意,这个问题是在我意识到更改获取类型可以解决问题之前首次发布的,因此问题现在已从 "How can I fix this" 转移到 "why is this a problem?"
BAR_TABLE.foo_pk 列上缺少索引会降低急切获取的速度,因为将执行完整的 table 扫描以加载与每个 FOO 实体关联的 BAR 实体,
首先,如果获取是急切的,这意味着延迟加载是错误的,那么只要加载 Foo_Entity 就会加载 Bar_Entity。因此,要么删除查询中的连接,要么使获取变得懒惰。两者都有是多余的。
第二,关于减速。由于您正在打开有状态会话。由于休眠一级缓存,每个对象都缓存在内存中。在这种情况下,放慢速度与懒惰、渴望或加入无关。减速是由于休眠在缓存(内存)中保存的对象数量。尝试使用无状态会话。然后减速应该消失。请参考以下URL
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html
我正在使用 ScrollableResults
对象从 table 滚动浏览大约 500,000 到 1,000,000 行。在滚动时,我使用每次迭代生成的实体创建一个不同的实体,并使用 session.save()
来保留该对象。下面是示例代码,其中实际代码更复杂但本质上做同样的事情。
Session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
ScrollableResults results = session.createQuery("from Foo_Table f join f.bars b")
.scroll(ScrollMode.FORWARD_ONLY);
int i = 0;
while(results.next())
{
Foo foo = (Foo) results.get(0);
Bar bar = new Baz(foo);
bar.setFoo(foo);
session.save(bar)
if(i % 50 == 0)
{
session.flush();
session.clear();
}
}
tx.commit();
session.close();
重要实体:
@Entity
@Table(name = "FOO_TABLE")
public class Foo_Entity implements Serializable {
@Id
@Column(name = "Foo_ID", nullable=false)
private String id;
@OneToMany(fetch = FetchType.EAGER, //FetchType.LAZY fixes the slow down
mappedBy = "fooParent", cascade = CascadeType.ALL)
private Set<Bar> bar_entities = new HashSet<>(0);
}
@Entity
@Table(name = "BAR_TABLE")
public class Bar_Entity implements Serializable {
@Id
@GeneratedValue
@Column(name="Id")
private Long id;
@ManyToOne
@JoinColumn(name="foo_pk")
private Foo fooParent;
// setFoo, getFoo...
}
当我为这个事务计时时,运行 时间从每 500 次迭代大约 100 毫秒开始,但在大约 20,000 次迭代后逐渐增加到每 500 次迭代几秒。结果,该事务的性能极差。唯一需要时间的代码行是 results.next()
,它的执行时间越来越长。
如果我将 Foo 中 Bar 实体的获取类型从 eager 更改为 lazy,问题就解决了。我不明白为什么对尚未填充的集合使用急切获取类型会导致滚动浏览包含该关系的实体时出现问题。在 session.flush() 上滚动期间确实填充了集合,但在我的场景中,集合通常只填充了一到两个元素,这就是为什么我更愿意将此获取类型设置为 eager.
有谁知道为什么在这种特定情况下会出现这种减速?
请注意,这个问题是在我意识到更改获取类型可以解决问题之前首次发布的,因此问题现在已从 "How can I fix this" 转移到 "why is this a problem?"
BAR_TABLE.foo_pk 列上缺少索引会降低急切获取的速度,因为将执行完整的 table 扫描以加载与每个 FOO 实体关联的 BAR 实体,
首先,如果获取是急切的,这意味着延迟加载是错误的,那么只要加载 Foo_Entity 就会加载 Bar_Entity。因此,要么删除查询中的连接,要么使获取变得懒惰。两者都有是多余的。
第二,关于减速。由于您正在打开有状态会话。由于休眠一级缓存,每个对象都缓存在内存中。在这种情况下,放慢速度与懒惰、渴望或加入无关。减速是由于休眠在缓存(内存)中保存的对象数量。尝试使用无状态会话。然后减速应该消失。请参考以下URL
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html