Ошибка 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:)
С наилучшими пожеланиями!
Майкл