Ошибка SQL-запроса NHibernate

Я использую LINQ to NH, чтобы получить кучу данных при запуске приложения. Я специально добавил ToList(), чтобы принудительно выполнить немедленное выполнение запроса:

Group group = GetGroup();
Log.Info("started");
var list = Session.Linq<Data>()
    .Where(p => p.Group.Id == group.Id)
    .OrderByDescending(p => p.Stamp.Counter) /* Stamp is composite mapping */
    .Select(p => new
    {
        Counter = p.Stamp.Counter,
        Status = p.Status,
    })
    .Take(4000)
    .ToList();
Log.Info("done");

Проверка журнала DEBUG для NHibernate.SQL logger дает следующий SQL, как и ожидалось (и этот же запрос появляется в SQL Profiler, когда я запускаю мониторинг):

SELECT top 4000 this_.Counter as y0_, this_.Status as y1_
FROM [Data] this_ 
LEFT OUTER JOIN [Group] group1_ ON this_.Group_id=group1_.Id
WHERE group1_.Id = @p0 
ORDER BY this_.Counter desc; @p0 = 1

Проблема заключается в том, что этот запрос занимает 2 минуты для завершения при вызове из моего приложения по сравнению с 0,5 с при выполнении в SSMS! Фактически, пока приложение ожидает завершения запроса, я могу выполнить его в SSMS и получить результаты мгновенно.

Как вы думаете, откуда эта разница?

Ответ 1

В Sinces нет информации о вашем приложении, я могу только догадываться.

Проблемы производительности с NH обычно возникают из-за сброса кеша. Перед каждым запросом кеш очищается. Когда на сессии есть много объектов, это может занять довольно много времени. Попробуйте следующее:

Log.Info("Flushing");
Session.Flush();
Session.FlushMode = FlushMode.Never;

Log.Info("Query");
var list = Session.Linq<Data>()
    //...
Log.Info("Done");
// for production code, this belongs into a finally block
Session.FlushMode = FlushMode.Auto; 

Если это действительно проблема с промывкой, вам необходимо вручную очистить ее в определенных точках транзакции. Будьте внимательны при выключении автопотока. Это может вызвать уродливые побочные эффекты. Это очень специфично для вашей транзакции, и я не могу сказать вам, как правильно ее реализовать. Вы также можете использовать StatelessSession, но для меня он никогда не работал (у него есть некоторые ограничения). Вы также можете очистить сеанс, что также требует, чтобы вы точно знали, что делаете.

Если это не проблема с промывкой, ее трудно отследить. Используйте Profiler, чтобы узнать, действительно ли это занимает время в запросе SQL-сервера. Это может быть даже проблема кэширования на сервере SQL. В этом случае при первом выполнении запроса требуется несколько минут, но только секунды второй раз. Создание соответствующих индексов может помочь. Здесь я перестаю гадать...

Ответ 2

Мое предположение, что есть некоторые перехватчики, которые замедляют материализацию объектов или интенсивную загрузку (т.е. проблема N + 1).

Я провел несколько тестов, и даже 30 000 объектов не могут замедлить получение списка объектов (с локальной машины 500 мс, чтобы получить список из 30000 объектов, с удаленного db - 4 секунды).

Ответ 3

Существует несколько возможных причин:

  • Вы загружаете по меньшей мере 4000 объектов в память, увлажняя их, а также NHibernate должен контролировать каждый загруженный объект в кеше сеанса и 1-го уровня
  • Я не видел ваших сопоставлений, но очень вероятно, что в какой-то момент есть какая-то интересная загрузка, которая также будет спамить другие запросы и загрузку других объектов и т.д.

Они приходят с моей головы, я мог бы больше. Также проверьте, не установлен ли уровень журнала NHibernate в DEBUG, он ОЧЕНЬ подробный и может потреблять много ресурсов.

Ответ 4

Хороший момент из проекта сегодня:

Я искал около недели по той причине, почему мой запрос nHibernate (многокритериальное использование фьючерсов для загрузки некоторых коллекций) занял 11 секунд (продолжительность в профилировщике MSSQL) и около 2 секунд, если я выполняю точно такой же комбинированный запрос в SSMS.

Решение было: у меня было активировано несколько журналов, чтобы запустить профайлер Ayendes. DLL NHProf, где отсутствует, но: Некоторые методы GetRows в nHibernate запускают логические вызовы во время гидратации. И разница была: 9 секунд!

Я просто прокомментировал вызов конфигурации log4net, и задержка почти исчезла.

У меня было около 14 000 экземпляров плюс 60 000 записей коллекции HasMany. Гидратация занимает теперь 0,6 секунды, потому что оператор SQL занимает 2 секунды (это еще одна история оптимизации).

И: Я думаю, что продолжительность гидратации вместе с длительностью выполнения запроса показана в столбце "длительность" профайлера SQL.

Еще одна история 2 недели назад: Планы выполнения в профилировщике SQL были отличны от тех, которые были выполнены при выполнении запроса в SSMS. Причиной этого было то, что я использовал поставщика OLEDB в nHibernate. Я переключился на соединения ADO, и план выполнения был таким же. Я нашел это при просмотре столбца "версия протокола" в профилировщике MS SQL.

Существует так много причин для ошибок производительности за пределами n + 1:)

С наилучшими пожеланиями! Майкл