15

次のように、子コレクションをフェッチする非常に単純な条件クエリがあります。

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を使用して、コールドキャッシュを使用してデータベースへのラウンドトリップが(予想どおりに)1回だけ行われることを確認しました。ただし、連続して実行すると、次のよう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)。

グーグルは、次のような多くの古代の問題を明らかにしました。

ただし、これらはすべてかなり前に修正されたようであり、使用するプロバイダーに関係なく、同じ悪い動作が発生します。

SysCacheとSysCache2に関するnhibernate.infoのドキュメントを読みましたが、不足しているものはないようです。cacheRegionクエリに関係するすべてのテーブルのファイルに行を追加しようとしましWeb.configたが、何も変更されません(そして、これらの要素はキャッシュを無効にするだけなので、とにかく問題にはなりません)。

すべてが修正/解決されたように見えるこれらの超古い問題のすべてで、これはおそらくNHibernateのバグではない可能性あると思います、それは私が間違っていることであるに違いありません。しかし、何ですか?

NHibernateのフェッチ命令を第2レベルのキャッシュと組み合わせるときに特別なことをする必要がありますか?ここで何が欠けていますか?

4

2 に答える 2

37

私はなんとかこれを理解したので、他の人々は最終的にまっすぐな答えを得ることができます:

要約すると、私はしばらくの間、第2レベルのキャッシュとクエリキャッシュの違いについて混乱してきました。ジェイソンの答えは技術的には正しいですが、どういうわけか私にはクリックされませんでした。これが私がそれを説明する方法です:

  • クエリキャッシュは、クエリによって発行されたエンティティを追跡します。結果セット全体をキャッシュするわけではありません。これはSession.Load、遅延ロードされたエンティティでを実行するのと同じです。存在することを認識/期待しますが、特に要求されない限り、それに関する他の情報を追跡しませんその時点で、実際に実際のエンティティがロードされます

  • 第2レベルのキャッシュは、各エンティティの実際のデータを追跡します。NHibernateがIDでエンティティをロードする必要がある場合(遅延ロードされた関係、または上記の場合、キャッシュされたクエリの一部であるエンティティの「参照」により)、第2レベルで検索されますSession.LoadSession.Get最初にキャッシュします。

もちろん、これは後から考えると完全に理にかなっていますが、「クエリキャッシュ」と「第2レベルのキャッシュ」という用語が非常に多くの場所でほぼ同じ意味で使用されていると聞いても、それほど明白ではありません。

基本的に、クエリキャッシュで期待される結果を確認するには、それぞれ2つの設定の2つのセットを構成する必要があります。

1.両方のキャッシュを有効にします

XML構成では、これは次の2行を追加することを意味します。

<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レベルのキャッシュは有効にしません。

2.各エンティティのキ​​ャッシュを有効にします

第2レベルのキャッシュを有効にするだけでは、ほとんど何も起こりません。ここで、私はつまずきました。第2レベルのキャッシュは、有効にするだけでなく、キャッシュする個々のエンティティクラスごとに構成する必要があります

XMLでは、これは<class>要素内で行われます。

<cache usage="read-write"/>

Fluent NHibernate(非自動マップ)では、ClassMapコンストラクターまたは残りのマッピングコードを配置する場所で実行されます。

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

これは、キャッシュされるすべてのエンティティに対して実行する必要があります。慣例として1つの場所にセットアップすることはおそらく可能ですが、リージョンを使用する機能をほとんど見逃してしまいます(ほとんどのシステムでは、構成データほどトランザクションデータをキャッシュしたくありません)。

以上です。本当に難しいことではありませんが、特にFNHの場合、適切で完全な例を見つけるのは驚くほど困難です。


最後のポイント:これの自然な結果は、クエリキャッシュで使用すると、熱心な結合/フェッチ戦略が非常に予測不可能になることです。どうやら、NHibernateがクエリがキャッシュされていることを確認した場合、実際のエンティティのすべてまたは一部がキャッシュされているかどうかを最初にチェックすることは努力もしません。それはほとんどそれらがそうであると仮定し、それぞれを個別にロードしようとします。

これがSELECTN+1災害の理由です。NHが、エンティティが第2レベルのキャッシュにないことに気づき、フェッチや先物などを使用して、記述どおりにクエリを正常に実行した場合、それほど大きな問題にはなりません。しかし、それはしません。代わりに、すべてのエンティティ、その関係、そのサブリレーション、およびそのサブサブリレーションなどを一度に1つずつロードしようとします。

したがって、グラフ全体のすべてのエンティティに対してキャッシュを明示的に有効にしない限り、クエリキャッシュを使用してもほとんど意味がありません。その場合でも、(有効期限、依存関係などによって)非常に注意する必要があります。 )キャッシュされたクエリは、取得するはずのエンティティよりも長持ちしません。そうしないと、パフォーマンスが低下するだけです。

于 2012-01-07T01:01:06.587 に答える
4

キャッシュされたクエリは、エンティティのIDのみを保存し、エンティティの値は保存しません。キャッシュされたエンティティ内では、関連するエンティティのIDのみがキャッシュされます。したがって、関連するすべてのエンティティをキャッシュせず、関連するエンティティをキャッシュ済みとしてマークしないと、n+1を選択することになります。

于 2012-01-06T16:51:57.890 に答える