NHibernate事务下大数据查询
Querying over large data under NHibernate transaction
我知道即使读取数据也应该使用显式事务,但我无法理解为什么下面的代码 运行 在 NHibernate 事务下要慢得多(而不是 运行ning 没有它)
session.BeginTransaction();
var result = session.Query<Order>().Where(o=>o.OrderNumber > 0).Take(100).ToList();
session.Transaction.Commit();
如果需要,我可以 post 更详细的 UT 代码,但如果我查询超过 50,000 条订单记录,在 NHibernate 的显式事务下,此查询到 运行 需要大约 1 秒,并且需要没有一个只有大约 15/20 毫秒。
2019 年 1 月 15 日更新
下面是详细代码
[Test]
public void TestQueryLargeDataUnderTransaction()
{
int count = 50000;
using (var session = _sessionFactory.OpenSession())
{
Order order;
// write large amount of data
session.BeginTransaction();
for (int i = 0; i < count; i++)
{
order = new Order {OrderNumber = i, OrderDate = DateTime.Today};
OrderLine ol1 = new OrderLine {Amount = 1 + i, ProductName = $"sun screen {i}", Order = order};
OrderLine ol2 = new OrderLine {Amount = 2 + i, ProductName = $"banjo {i}", Order = order};
order.OrderLines = new List<OrderLine> {ol1, ol2};
session.Save(order);
session.Save(ol1);
session.Save(ol2);
}
session.Transaction.Commit();
Stopwatch s = new Stopwatch();
// read the same data
session.BeginTransaction();
var result = session.Query<Order>().Where(o => o.OrderNumber > 0).Skip(0).Take(100).ToList();
session.Transaction.Commit();
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
}
}
您的 for 循环迭代 50000 次,每次迭代都会创建 3 个对象。因此,当您第一次调用 Commit() 时,会话知道它将在提交时间(或更早)刷新到数据库的大约 150000 个对象(取决于您的 ID 生成器策略和刷新模式)。
到目前为止,还不错。 NHibernate 不一定优化以处理会话中的这么多对象,但只要小心,它是可以接受的。
关于问题...
认识到提交事务不会从会话中删除 150000 个对象很重要。
当您稍后执行查询时,它会注意到它在一个事务中,在这种情况下,默认情况下,将执行 "auto-flushing"。这意味着在将 SQL 查询发送到数据库之前,NHibernate 将检查会话已知的任何对象是否有可能影响查询结果的更改(这有点简化)。如果发现此类更改,它们将在执行实际 SQL 查询之前传输到数据库。这可确保执行的查询能够根据同一会话中所做的更改进行过滤。
您注意到的额外秒数是 NHibernate 遍历会话已知的 150000 个对象以检查任何更改所花费的时间。 NHibernate 的主要用例很少涉及超过几十或几百个对象,在这种情况下,检查更改所需的时间可以忽略不计。
您可以为查询使用新的会话以看不到此效果,或者您可以在第一次提交后立即调用 session.Clear()。 (请注意,对于生产代码,session.Clear() 可能很危险。)
附加:自动刷新发生在查询时,但仅在事务内发生。可以使用 session.FlushMode 控制此行为。在自动刷新期间,NHibernate 将旨在仅刷新可能影响查询结果的对象(即哪些数据库表受到影响)。
关于保持会话,还有一个额外的影响需要注意。考虑这段代码:
using (var session = _sessionFactory.OpenSession())
{
Order order;
session.BeginTransaction();
for (int i = 0; i < count; i++)
{
// Your code from above.
}
session.Transaction.Commit();
// The order variable references the last order created. Let's modify it.
order.OrderDate = DateTime.Today.AddDays(4);
session.BeginTransaction();
var result = session.Query<Order>().Skip(0).Take(100).ToList();
session.Transaction.Commit();
}
第一次调用 Commit() 后更改订单日期会发生什么情况?尽管对象修改本身发生在事务开始之前,但在第二个事务中执行查询时,该更改将持久保存到数据库中。相反,如果你删除了第二个事务,那么那个修改当然不会被持久化。
有多种方法可以管理可用于不同目的的会话和事务。然而,到目前为止,最简单的方法是始终遵循这个简单的工作单元模式:
- 打开会话。
- 立即开启交易。
- 执行合理的工作量。
- 提交或回滚事务。
- 处置交易。
- 处理会话。
- 放弃使用会话加载的所有对象。此时他们仍然可以
在内存中使用,但不会持久化任何更改。得到更安全
摆脱他们。
我知道即使读取数据也应该使用显式事务,但我无法理解为什么下面的代码 运行 在 NHibernate 事务下要慢得多(而不是 运行ning 没有它)
session.BeginTransaction();
var result = session.Query<Order>().Where(o=>o.OrderNumber > 0).Take(100).ToList();
session.Transaction.Commit();
如果需要,我可以 post 更详细的 UT 代码,但如果我查询超过 50,000 条订单记录,在 NHibernate 的显式事务下,此查询到 运行 需要大约 1 秒,并且需要没有一个只有大约 15/20 毫秒。
2019 年 1 月 15 日更新 下面是详细代码
[Test]
public void TestQueryLargeDataUnderTransaction()
{
int count = 50000;
using (var session = _sessionFactory.OpenSession())
{
Order order;
// write large amount of data
session.BeginTransaction();
for (int i = 0; i < count; i++)
{
order = new Order {OrderNumber = i, OrderDate = DateTime.Today};
OrderLine ol1 = new OrderLine {Amount = 1 + i, ProductName = $"sun screen {i}", Order = order};
OrderLine ol2 = new OrderLine {Amount = 2 + i, ProductName = $"banjo {i}", Order = order};
order.OrderLines = new List<OrderLine> {ol1, ol2};
session.Save(order);
session.Save(ol1);
session.Save(ol2);
}
session.Transaction.Commit();
Stopwatch s = new Stopwatch();
// read the same data
session.BeginTransaction();
var result = session.Query<Order>().Where(o => o.OrderNumber > 0).Skip(0).Take(100).ToList();
session.Transaction.Commit();
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
}
}
您的 for 循环迭代 50000 次,每次迭代都会创建 3 个对象。因此,当您第一次调用 Commit() 时,会话知道它将在提交时间(或更早)刷新到数据库的大约 150000 个对象(取决于您的 ID 生成器策略和刷新模式)。
到目前为止,还不错。 NHibernate 不一定优化以处理会话中的这么多对象,但只要小心,它是可以接受的。
关于问题...
认识到提交事务不会从会话中删除 150000 个对象很重要。
当您稍后执行查询时,它会注意到它在一个事务中,在这种情况下,默认情况下,将执行 "auto-flushing"。这意味着在将 SQL 查询发送到数据库之前,NHibernate 将检查会话已知的任何对象是否有可能影响查询结果的更改(这有点简化)。如果发现此类更改,它们将在执行实际 SQL 查询之前传输到数据库。这可确保执行的查询能够根据同一会话中所做的更改进行过滤。
您注意到的额外秒数是 NHibernate 遍历会话已知的 150000 个对象以检查任何更改所花费的时间。 NHibernate 的主要用例很少涉及超过几十或几百个对象,在这种情况下,检查更改所需的时间可以忽略不计。
您可以为查询使用新的会话以看不到此效果,或者您可以在第一次提交后立即调用 session.Clear()。 (请注意,对于生产代码,session.Clear() 可能很危险。)
附加:自动刷新发生在查询时,但仅在事务内发生。可以使用 session.FlushMode 控制此行为。在自动刷新期间,NHibernate 将旨在仅刷新可能影响查询结果的对象(即哪些数据库表受到影响)。
关于保持会话,还有一个额外的影响需要注意。考虑这段代码:
using (var session = _sessionFactory.OpenSession())
{
Order order;
session.BeginTransaction();
for (int i = 0; i < count; i++)
{
// Your code from above.
}
session.Transaction.Commit();
// The order variable references the last order created. Let's modify it.
order.OrderDate = DateTime.Today.AddDays(4);
session.BeginTransaction();
var result = session.Query<Order>().Skip(0).Take(100).ToList();
session.Transaction.Commit();
}
第一次调用 Commit() 后更改订单日期会发生什么情况?尽管对象修改本身发生在事务开始之前,但在第二个事务中执行查询时,该更改将持久保存到数据库中。相反,如果你删除了第二个事务,那么那个修改当然不会被持久化。
有多种方法可以管理可用于不同目的的会话和事务。然而,到目前为止,最简单的方法是始终遵循这个简单的工作单元模式:
- 打开会话。
- 立即开启交易。
- 执行合理的工作量。
- 提交或回滚事务。
- 处置交易。
- 处理会话。
- 放弃使用会话加载的所有对象。此时他们仍然可以 在内存中使用,但不会持久化任何更改。得到更安全 摆脱他们。