我知道即使在读取数据时也应使用显式事务,但是我无法理解为什么以下代码在NHibernate事务下运行得慢得多(而不是在没有它的情况下运行)
session.BeginTransaction();
var result = session.Query<Order>().Where(o=>o.OrderNumber > 0).Take(100).ToList();
session.Transaction.Commit();
如果需要,我可以发布更详细的UT代码,但是如果我要查询50,000多个订单记录,则在NHibernate的显式事务下运行此查询大约需要1秒钟,而没有一个,只需要15/20毫秒。
更新1/15/2019 这是详细的代码
[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);
}
}
答案 0 :(得分:2)
您的for循环迭代50000次,每次迭代都会创建3个对象。因此,当您第一次调用Commit()时,会话知道它将在Commit时(或更早的时间)刷新到数据库中的150000个对象(取决于您的ID生成器策略和刷新模式)。
到目前为止,太好了。 NHibernate不一定要优化以处理会话中的许多对象,但是只要小心一点就可以接受。
解决问题...
重要的是要认识到提交事务不会从会话中删除150000个对象。
稍后执行查询时,它将注意到它在事务内部,在这种情况下,默认情况下将执行“自动刷新”。这意味着在将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()之后更改订单日期会发生什么?尽管对象修改本身是在事务启动之前发生的,但在第二个事务中执行查询时,该更改将保留在数据库中。相反,如果删除第二笔交易,则该修改当然不会保留。
有多种管理会话和事务的方法可以用于不同的目的。但是,到目前为止,最简单的方法就是始终遵循这种简单的工作单元模式: