私もこの問題にぶつかりました。基本的には、IN句に可変数の値が含まれ、Hibernateがそれらのクエリプランをキャッシュしようとすることになります。
このトピックに関する2つのすばらしいブログ投稿があります。最初の投稿:
次のような句内クエリを使用するプロジェクトでHibernate4.2とMySQLを使用する:
select t from Thing t where t.id in (?)
Hibernateはこれらの解析されたHQLクエリをキャッシュします。具体的には、Hibernate
SessionFactoryImpl
QueryPlanCache
がありますqueryPlanCache
を使用 およびparameterMetadataCache
。しかし、これは、節内のパラメーターの数が多く、変化する場合に問題になることが判明しました。これらのキャッシュは、個別のクエリごとに大きくなります。したがって、6000パラメータを使用したこのクエリは6001と同じではありません。
句内のクエリは、コレクション内のパラメータの数に拡張されます。 x10_、x11_などの生成された名前を含むメタデータは、クエリの各パラメータのクエリプランに含まれています。
節内のパラメーターカウントの数に4000の異なるバリエーションがあり、それぞれが平均4000のパラメーターを持っていると想像してください。各パラメータのquerymetadataは、ガベージコレクションできないため、すぐにメモリに追加され、ヒープがいっぱいになります。
これは、クエリパラメータカウントのさまざまなバリエーションがすべてキャッシュされるか、JVMがヒープメモリを使い果たしてjava.lang.OutOfMemoryError:Javaヒープスペースのスローを開始するまで続きます。
節内を回避することはオプションであり、パラメータに固定のコレクションサイズ(または少なくとも小さいサイズ)を使用することもできます。
クエリプランキャッシュの最大サイズの設定については、プロパティ
hibernate.query.plan_cache_max_size
を参照してください。 、デフォルトは2048
(多くのパラメータを持つクエリには大きすぎます)。
そして2番目(最初からも参照):
Hibernateは、HQLステートメント(asstring)をクエリプランにマップするキャッシュを内部的に使用します。キャッシュは、デフォルトで2048要素(構成可能)に制限された境界マップで構成されます。すべてのHQLクエリはこのキャッシュを介してロードされます。ミスした場合、エントリは自動的にキャッシュに追加されます。これにより、スラッシングの影響を非常に受けやすくなります。つまり、新しいエントリを再利用せずに常にキャッシュに入れて、キャッシュによるパフォーマンスの向上を防ぐことができます(キャッシュ管理のオーバーヘッドがいくらか追加されます)。さらに悪いことに、この状況を偶然に検出することは困難です。問題があることに気付くには、キャッシュを明示的にプロファイリングする必要があります。これを後でどのように行うことができるかについて、いくつかの言葉を述べます。
したがって、キャッシュスラッシングは、新しいクエリが高率で生成されることによって発生します。これは、多数の問題が原因で発生する可能性があります。私が見た中で最も一般的な2つは、-パラメータとして渡される代わりにJPQLステートメントでパラメータがレンダリングされる休止状態のバグと「in」句の使用です。
hibernateにはいくつかのあいまいなバグがあるため、パラメーターが正しく処理されず、JPQLqueryにレンダリングされる場合があります(例として、HHH-6280を確認してください)。このような欠陥の影響を受けるクエリがあり、それが高速で実行される場合、生成される各JPQLクエリはほぼ一意であるため(たとえば、エンティティのIDを含む)、クエリプランキャッシュが破棄されます。
2番目の問題は、休止状態が「in」句を使用してクエリを処理する方法にあります(たとえば、会社のIDフィールドが1、2、10、18のいずれかであるすべての個人エンティティを指定します)。 「in」句のパラメーターの数が異なるごとに、hibernateは異なるクエリを生成します。たとえば、
select x from Person x where x.company.id in (:id0_)
1つのパラメーターの場合、select x from Person x where x.company.id in (:id0_, :id1_)
2パラメータなど。クエリプランキャッシュに関する限り、これらのクエリはすべて異なると見なされ、キャッシュスラッシングが発生します。特定の数のパラメータのみを生成するユーティリティクラスを作成することで、この問題を回避できる可能性があります。 1,10、100、200、500、1000。たとえば、22個のパラメーターを渡すと、22個のパラメーターがinitに含まれ、残りの78個のパラメーターが不可能な値(たとえば、IDの場合は-1)に設定された100個の要素のリストが返されます。外部キーに使用されます)。私はこれが醜いハックであることに同意しますが、仕事を成し遂げることができます。その結果、キャッシュには最大6つの一意のクエリしか含まれないため、スラッシングが減少します。では、どのようにして問題があることを確認しますか?追加のコードを記述して、キャッシュ内のエントリ数を使用してメトリックを公開することができます。 JMXを介して、ログを調整し、ログを分析します。アプリケーションを変更したくない(または変更できない)場合は、ヒープをダンプして、このOQLクエリをアプリケーションに対して実行できます(たとえば、マットを使用)。
SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l
。ヒープ上のクエリプランキャッシュに現在配置されているすべてのクエリを出力します。前述の問題のいずれかによって影響を受けているかどうかを見つけるのは非常に簡単です。パフォーマンスへの影響に関する限り、それはあまりにも多くの要因に依存しているため、言うのは難しいです。新しいHQLクエリプランの作成に10〜20ミリ秒のオーバーヘッドが発生する非常に些細なクエリを見てきました。一般に、どこかにキャッシュがある場合は、それには十分な理由があるはずです。ミスはおそらく高額なので、できるだけミスを回避するようにしてください。大事なことを言い忘れましたが、データベースは大量の一意のSQLステートメントも処理する必要があります。これにより、データベースはそれらを解析し、それらごとに異なる実行プランを作成する可能性があります。