Запрос больших объемов данных в транзакции NHibernate - PullRequest
0 голосов
/ 14 января 2019

Я понимаю, что явные транзакции следует использовать даже для чтения данных, но я не могу понять, почему приведенный ниже код работает намного медленнее при транзакции NHibernate (в отличие от выполнения без нее)

session.BeginTransaction(); var result = session.Query<Order>().Where(o=>o.OrderNumber > 0).Take(100).ToList(); session.Transaction.Commit();

Я могу опубликовать более подробный код UT, если это необходимо, но если я запрашиваю более 50 000 записей Order, этот запрос будет выполняться примерно за 1 секунду при явной транзакции NHibernate, а без него - всего около 15/20 мс.

Обновление от 15.01.2009 Вот подробный код

[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);
            }
        }

1 Ответ

0 голосов
/ 15 января 2019

Ваш цикл for выполняет итерации 50000 раз и для каждой итерации создает 3 объекта. Поэтому, когда вы достигнете первого вызова Commit (), сеанс будет знать о 150000 объектах, которые он будет сбрасывать в базу данных во время фиксации (или раньше) (в зависимости от вашей политики генератора идентификаторов и режима сброса).

Пока все хорошо. 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 ()? Это изменение будет сохранено в базе данных при выполнении запроса во второй транзакции, несмотря на тот факт, что само изменение объекта произошло до того, как транзакция была запущена. И наоборот, если вы удалите вторую транзакцию, эта модификация, конечно, не будет сохранена.

Существует несколько способов управления сеансами и транзакциями, которые можно использовать для разных целей. Однако, безусловно, самым простым является всегда следовать этому простому шаблону единиц работы:

  1. Открытая сессия.
  2. Сразу же открыть транзакцию.
  3. Выполните разумное количество работы.
  4. Транзакция фиксации или отката.
  5. Утилизировать транзакцию.
  6. Утилизировать сеанс.
  7. Отменить все объекты, загруженные с помощью сеанса. На данный момент они все еще могут использоваться в памяти, но любые изменения не будут сохранены. Безопаснее просто получить избавиться от них.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...