【问题标题】:How do I make NHibernate cache fetched child collections?如何使 NHibernate 缓存获取子集合?
【发布时间】:2012-01-06 16:42:49
【问题描述】:

我有一个相当简单的条件查询来获取子集合,如下所示:

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)。

谷歌搜索发现了许多古老的问题,例如:

但是,这些似乎都在很久以前就已经修复了,无论我使用哪个提供商,我都会遇到同样的不良行为。

我已经阅读了nhibernate.info documentation on SysCache and SysCache2,似乎没有任何我遗漏的内容。我已经尝试将cacheRegion 行添加到查询中涉及的所有表的Web.config 文件中,但它没有改变任何东西(并且AFAIK 这些元素只是为了使缓存无效,所以无论如何,它们应该无关紧要)。

鉴于所有这些似乎都已修复/解决的超级老问题,我认为这不可能仍然是 NHibernate 中的错误,它一定是我正在做的事情错误的。但是什么?

在将 NHibernate 中的 fetch 指令与二级缓存相结合时,我需要做些什么特别的事情吗?我在这里错过了什么?

【问题讨论】:

  • 你使用的是无状态会话吗?
  • @Origin:不,实际上这是未来查询序列的一部分,并且在无状态会话中不可用。无论如何,无状态会话不会以这种方式运行,它只会每次都执行原始查询。

标签: .net nhibernate syscache2


【解决方案1】:

我确实设法弄清楚了,所以其他人终于可以得到一个直接的答案:

总结一下,对于二级缓存和查询缓存的区别,我已经困惑了一段时间;杰森的回答在技术上是正确的,但不知何故并没有为我点击。以下是我的解释:

  • 查询缓存跟踪查询发出的实体。它确实缓存整个结果集。这相当于在延迟加载的实体上执行Session.Load;它知道/期望一个存在,但不跟踪任何关于它的其他信息,除非特别询问,此时它将实际加载真实实体。

  • 二级缓存跟踪每个实体的实际数据。当 NHibernate 需要通过其 ID 加载任何实体时(借助 Session.LoadSession.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,因为(在此发布时)Fluent NHibernate wiki page从未提及;有几个启用查询缓存但不启用二级缓存的示例!

2。为每个实体启用缓存

仅启用二级缓存几乎没有任何作用,这就是我被绊倒的地方。二级缓存不仅要启用,而且必须为您想要缓存的每个单独的实体类配置

在 XML 中,这是在 &lt;class&gt; 元素内完成的:

<cache usage="read-write"/>

在 Fluent NHibernate(非自动映射)中,它是在 ClassMap 构造函数或您放置其余映射代码的任何位置完成的:

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

这必须为要被缓存的每个实体完成。作为惯例,可能可以在一个地方设置,但是您几乎会错过使用区域的能力(在大多数系统中,您不希望缓存事务数据和配置数据一样多)。

就是这样。确实并不难,但要找到一个好的、完整的例子却出奇地难,尤其是对于 FNH。


最后一点:这样做的自然结果是 当与查询缓存一起使用时,它使得急切的连接/获取策略变得非常不可预测。显然,如果 NHibernate 发现查询被缓存,它会不费吹灰之力首先检查是否所有甚至任何实际实体都被缓存了。它几乎只是假设它们是,并尝试单独加载每个。

这就是 SELECT N+1 灾难的原因;如果 NH 注意到实体不在二级缓存中并且只是正常执行查询,如所写的那样,使用 fetches 和 futures 等等,这没什么大不了的。但它不会那样做;相反,它会尝试加载每个实体、它的关系、它的子关系和它的子子关系,等等,一次一个

因此,除非您为 entire 图中的 all 实体显式启用缓存,否则使用查询缓存几乎没有任何意义,即便如此,您' 需要非常小心(通过过期、依赖等方式)缓存的查询不会超过它们应该检索的实体,否则最终只会使性能变差。

【讨论】:

    【解决方案2】:

    缓存查询仅存储实体的 ID,而不是实体的值。在缓存实体中,仅缓存相关实体的 ID。因此,如果您不缓存所有涉及的实体并将相关实体标记为已缓存,您最终可能会选择 n+1。

    【讨论】:

    • “将相关实体标记为缓存”是什么意思?如何做到这一点? Ayende 在他的 cmets 中也做了同样模糊的引用,但没有给出任何例子……
    • @JasonMeckley:我遇到了同样的问题,并尝试了在互联网上找到的所有建议。请您详细说明一下好吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 2012-04-17
    • 2019-09-10
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多