Ответ 1
Мне удалось понять это, поэтому другие люди могут, наконец, получить прямой ответ:
Подводя итог, я некоторое время смущал разницу между кешем второго уровня и кешем запросов; Ответ Джейсона технически правильный, но он почему-то не нажимал на меня. Вот как я это объясню:
-
Кэш запросов отслеживает, какие объекты испускаются запросом. Он не кэширует весь набор результатов. Это эквивалентно выполнению
Session.Load
на ленивом носителе; он знает/ожидает, что он существует, но не отслеживает какую-либо другую информацию об этом, если специально не спросил, и в какой момент он фактически загрузит реальный объект. -
Кэш второго уровня отслеживает фактические данные для каждого объекта. Когда NHibernate необходимо загрузить любой объект по его идентификатору (в силу
Session.Load
,Session.Get
, lazy-load relationship, или, в случае выше, сущность "ссылка", эта часть кэшированного запроса), она будет сначала посмотрите в кеш второго уровня.
Конечно, это имеет смысл в ретроспективе, но это не так очевидно, когда вы слышите, что термины "кеш запросов" и "кеш второго уровня" используются почти взаимозаменяемо во многих местах.
По существу, есть два набора из двух параметров, которые вам нужно настроить, чтобы увидеть ожидаемые результаты с кешированием запросов:
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
выше, потому что (во время этой публикации) это никогда, упомянутое на странице Fluent NHibernate wiki; существует несколько примеров включения кеша запросов, но не кеша второго уровня!
2. Включить кеширование для каждого объекта
Простое включение кеша второго уровня практически ничего не значит, и вот тут я сработал. Кэш второго уровня должен быть не только включен, но и настроен для каждого отдельного класса сущности, который вы хотите кэшировать.
В XML это делается внутри элемента <class>
:
<cache usage="read-write"/>
В Fluent NHibernate (non-automap) это делается в конструкторе ClassMap
или где бы вы не поместили остальную часть вашего кода отображения:
Cache.ReadWrite().Region("Configuration");
Это должно быть сделано для каждого объекта, который будет кэшироваться. Вероятно, можно настроить в одном месте как соглашение, но тогда вы в значительной степени упускаете возможность использовать регионы (и в большинстве систем вы не хотите кэшировать транзакционные данные, а также данные конфигурации).
И что это. Это действительно не так сложно сделать, но на удивление трудно найти хороший, полный пример, особенно для FNH.
Один последний момент: естественным следствием этого является то, что он делает очень страшные стратегии соединения/выборки очень непредсказуемыми при использовании с кешем запросов. Очевидно, если NHibernate видит, что запрос кэшируется, он будет не прилагайте никаких усилий, чтобы проверить, если все или даже какие-либо из фактических объектов будут кэшированы. Это в значительной степени просто предполагает, что они есть, и пытается загрузить каждую из них по отдельности.
Это причина катастрофы SELECT N + 1; это не было бы большой сделкой, если бы NH заметил, что сущности не находятся в кэше второго уровня и просто выполняли запрос, как обычно, с помощью феттей и фьючерсов и так далее. Но это не так; вместо этого он пытается загрузить каждую сущность и ее отношения, а также ее подзадачи и ее под-отношения и т.д. по одному за раз.
Таким образом, использование кеша запросов почти не имеет смысла, если вы явно не включили кеширование для всех объектов на всем графике, и даже тогда вы захотите быть очень осторожными (путем истечения срока действия, зависимостей и т.д.), что кэшированные запросы не превзойдут сущности, которые они должны получить, иначе вы просто закончите тем, что ухудшаете производительность.