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() 后更改订单日期会发生什么情况?尽管对象修改本身发生在事务开始之前,但在第二个事务中执行查询时,该更改将持久保存到数据库中。相反,如果你删除了第二个事务,那么那个修改当然不会被持久化。

有多种方法可以管理可用于不同目的的会话和事务。然而,到目前为止,最简单的方法是始终遵循这个简单的工作单元模式:

  1. 打开会话。
  2. 立即开启交易。
  3. 执行合理的工作量。
  4. 提交或回滚事务。
  5. 处​​置交易。
  6. 处​​理会话。
  7. 放弃使用会话加载的所有对象。此时他们仍然可以 在内存中使用,但不会持久化任何更改。得到更安全 摆脱他们。