Как сделать, чтобы NHibernate извлекал данные из кэша - PullRequest
14 голосов
/ 06 января 2012

У меня довольно простой критерий запроса, который выбирает дочерние коллекции, например:

var order = Session.CreateCriteria<Order>()
    .Add(Restrictions.Eq("Id", id))
    .SetFetchMode("Customer", FetchMode.Eager)
    .SetFetchMode("Products", FetchMode.Eager)
    .SetFetchMode("Products.Category", FetchMode.Eager)
    .SetCacheable(true)
    .UniqueResult<Order>();

Используя NH Prof, я убедился, что для этого требуется всего одна поездка в базу данных (как и ожидалось) с холодным кешем; однако при последовательных выполнениях он извлекает из кэша только Order, а затем обращается к базе данных с помощью SELECT (N + 1) для каждого дочернего объекта в графе, например:

Cached query: SELECT ... FROM Order this_ left outer join Customer customer2 [...]
SELECT ... FROM Customer WHERE Id = 123;
SELECT ... FROM Products WHERE Id = 500;
SELECT ... FROM Products WHERE Id = 501;
...
SELECT ... FROM Categories WHERE Id = 3;

И так далее, и тому подобное. Ясно, что это не кэширование целого запроса или графика, а только корневая сущность. Первая строка «кэшированного запроса» на самом деле содержит все условия join, которые должны быть выполнены - это определенно правильно кеширует сам запрос , просто не сущности , по-видимому.

Я пробовал это с помощью SysCache, SysCache2 и даже провайдеров кеша HashTable, и мне всегда кажется, что это одно и то же поведение (NH версия 3.2.0).

Поиск в Google поднял ряд древних вопросов, таких как:

Однако все это, похоже, уже давно исправлено, и я получаю одинаковое плохое поведение независимо от того, какого провайдера я использую.

Я прочитал nhibernate.info документацию по SysCache и SysCache2 , и, похоже, я ничего не пропустил. Я попытался добавить cacheRegion строк в файл Web.config для всех таблиц, участвующих в запросе, но это ничего не меняет (и AFAIK эти элементы просто делают недействительными кэш, поэтому они в любом случае не имеет значения).

Со всеми этими супер-старыми проблемами, которые, кажется, все исправлены / решены, я думаю, что это не может все же быть ошибкой в ​​NHibernate, это должно быть что-то, что я делаю неправильно , Но что?

Есть ли что-то особенное, что мне нужно сделать при объединении инструкций выборки в NHibernate с кешем второго уровня? Что мне здесь не хватает?

Ответы [ 2 ]

37 голосов
/ 07 января 2012

Мне удалось выяснить это, так что другие люди, наконец, могут получить прямой ответ:

Подводя итог, я некоторое время путался в разнице между кешем второго уровня и кешем запросов; Ответ Джейсона технически верен, но почему-то меня это не задело. Вот как бы я это объяснил:

  • Кэш запроса отслеживает , какие объекты испускаются запросом. не кэширует весь набор результатов. Это эквивалентно выполнению Session.Load на лениво загруженной сущности; он знает / ожидает, что кто-то существует, но не отслеживает любую другую информацию о нем, если не будет специально задан вопрос, в какой момент он фактически загрузит реальную сущность.

  • Кэш второго уровня отслеживает фактические данные для каждого объекта. Когда NHibernate необходимо загрузить любую сущность по ее идентификатору (в силу Session.Load, Session.Get, отношения с отложенной загрузкой или, в случае выше, «ссылки» сущности, которая является частью кэшированного запроса), он будет сначала загляните в кеш второго уровня.

Конечно, это имеет смысл в ретроспективе, но это не так очевидно, когда вы слышите, что термины «кэш запросов» и «кэш второго уровня» используются почти взаимозаменяемо во многих местах.

По сути, существует два набора из двух параметров, каждый из которых необходимо настроить, чтобы увидеть ожидаемые результаты с кэшированием запросов:

1. Включить оба кэша

В конфигурации XML это означает добавление следующих двух строк:

<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>

В Fluent NHibernate это так:

.Cache(c => c
    .UseQueryCache()
    .UseSecondLevelCache()
    .ProviderClass<SysCacheProvider>())

Обратите внимание на UseSecondLevelCache выше, потому что (на момент публикации) это никогда , упомянутое на Свободной странице вики NHibernate ; Есть несколько примеров включения кэша запросов, но не кэша второго уровня!

2. Включить кэширование для каждой сущности

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

В XML это делается внутри элемента <class>:

<cache usage="read-write"/>

В Fluent NHibernate (не-autop) это делается в конструкторе ClassMap или в том месте, куда вы помещаете оставшуюся часть кода отображения:

Cache.ReadWrite().Region("Configuration");

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

И это все. Это действительно не так сложно сделать, но на удивление сложно найти хороший, полный пример, особенно для FNH.


И последнее замечание: естественным следствием этого является то, что делает стратегии непредсказуемого соединения / выборки очень непредсказуемыми при использовании с кешем запросов. Очевидно, что если NHibernate видит, что запрос кэшируется, он будет делать без усилий сначала проверить, кэшированы ли все или даже любые фактических сущностей. Он просто предполагает, что они есть, и пытается загрузить каждый из них по отдельности.

Это причина катастрофы SELECT N + 1; было бы не так уж и сложно, если бы NH заметил, что сущности отсутствуют в кэше второго уровня, а просто выполнил запрос нормально, как написано, с выборками, фьючерсами и так далее. Но это не делает этого; вместо этого он пытается загрузить каждую сущность, ее отношения, ее под-отношения, его под-под-отношения и т. д. по одному .

Таким образом, практически нет смысла использовать кеш запросов, если вы явно не включили кеширование для всех сущностей в графе весь , и даже тогда вы будетеЯ хочу быть очень осторожным (из-за истечения срока действия, зависимостей и т. д.), чтобы кэшированные запросы не переживали сущности, которые они должны извлекать, в противном случае вы просто ухудшите производительность.

4 голосов
/ 06 января 2012

кэшированный запрос хранит только идентификаторы сущностей, а не значения сущности.в кэшированном объекте кэшируются только идентификаторы связанных объектов.поэтому, если вы не кэшируете все вовлеченные объекты, а также отмечаете связанные объекты как кэшированные, вы можете в итоге выбрать n + 1.

...