我确实设法弄清楚了这一点,所以其他人终于可以得到直接的答案:
综上所述,我有一段时间对二级缓存和查询缓存的区别感到困惑;杰森的答案在技术上是正确的,但不知怎的,它并不适合我。我是这样解释的:
-
查询缓存会跟踪哪些实体由查询发出。确实如此not缓存整个结果集。这相当于做一个Session.Load
在延迟加载的实体上;它知道/预期存在但不跟踪any除非特别询问,否则它会实际加载其他信息real entity.
-
二级缓存跟踪实际的data对于每个实体。当 NHibernate 需要通过 ID 加载任何实体时(凭借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
上面因为(在发布本文时)它是never上提到的Fluent NHibernate wiki 页面 http://wiki.fluentnhibernate.org/Database_configuration;有几个启用查询缓存但不启用二级缓存的例子!
2.为每个实体启用缓存
仅启用二级缓存几乎没有任何作用,这就是我被绊倒的地方。二级缓存不仅必须启用,而且为您想要缓存的每个单独的实体类进行配置.
在 XML 中,这是在<class>
元素:
<cache usage="read-write"/>
在 Fluent NHibernate(非自动映射)中,它是在ClassMap
构造函数或放置其余映射代码的任何位置:
Cache.ReadWrite().Region("Configuration");
必须这样做every将被缓存的实体。作为一种约定,可能可以在一个地方进行设置,但是这样您就几乎错过了使用区域的能力(并且在大多数系统中,您不希望缓存事务数据与配置数据一样多)。
就是这样。这实际上并不难,但要找到一个好的、完整的例子却出人意料地困难,尤其是对于 FNH 来说。
最后一点:这样做的自然结果是当与查询缓存一起使用时,它使得急切连接/获取策略变得非常不可预测。显然,如果 NHibernate 发现查询已被缓存,它就会使没有任何努力首先检查是否all甚至any实际实体的数量被缓存。它几乎只是假设它们是,并尝试单独加载每一个。
这就是SELECT N+1灾难的原因;如果 NH 注意到实体不在二级缓存中并且只是按照所写的那样正常执行查询,并使用 fetch 和 future 等,那么这也没什么大不了的。但它并没有这样做;相反,它尝试加载每个实体及其关系、子关系、子子关系等等,一次一个.
因此,除非您明确启用了缓存,否则使用查询缓存几乎没有意义all中的实体entire图表,即使如此,您也需要非常小心(通过过期、依赖关系等),缓存的查询不会比它们应该检索的实体更持久,否则您最终将导致性能下降更差。