web-dev-qa-db-ja.com

エンティティフレームワークインクルードパフォーマンス

私は、Entity Frameworkのパフォーマンス、特にIncludesの使用と、さまざまなクエリの生成と実行の両方にかかる時間について見てきました。

私が行った変更について詳しく説明しますが、これらの仮定のいずれかが間違っていると思われる場合は修正してください。

まず、dbには約10,000アイテム(多くはありません)があり、データベースは大幅に正規化されています(これにより、ナビゲーションプロパティの数が大幅に増加します)。現在のアプローチはすべてを遅延ロードすることであり、1つのアイテムを要求すると数十のdb要求がスプールされる可能性があるため、特に大きなデータセットの場合、パフォーマンスは非常に低くなります。 (これは継承されたプロジェクトであり、ステップ1は大幅な再構築なしにパフォーマンスを改善しようとしています)

したがって、最初の手順は、クエリの結果を取得し、ナビゲーションプロパティのIncludesをそれらの結果にのみ適用することでした。これは技術的に2つのクエリを実行することを知っていますが、10,000アイテムが格納されていて、10アイテムのみを返したい場合は、それらの10アイテムのナビゲーションプロパティのみを含める方が理にかなっています。

次に、クエリ結果で複数のインクルードが使用され、その結果セットのサイズが非常に大きい場合でも、パフォーマンスが低下します。イージーロードをいつ行うべきか、レイジーロードをそのままにしておくべきかについて、私は実際的でした。次の変更は、クエリインクルードをバッチで読み込むことでした。

query.Include(q => q.MyInclude).Load();

これにより、パフォーマンスが大幅に改善されましたが、db呼び出し(インクルードのバッチごとに1つ)が大きいクエリよりも高速でしたが、Entity Frameworkがその大きなクエリを生成しようとするオーバーヘッドが少なくとも減少しました。

したがって、コードは次のようになります。

_    var query = ctx.Filters.Where(x => x.SessionId == id)
        .Join(ctx.Items, i => i.ItemId, fs => fs.Id, (f, fs) => fs);
    query
        .Include(x => x.ItemNav1)
        .Include(x => x.ItemNav2).Load();

    query
        .Include(x => x.ItemNav3)
        .Include(x => x.ItemNav4).Load();

    query
        .Include(x => x.ItemNav5)
        .Include(x => x.ItemNav6).Load();
_

現在、これはかなりのパフォーマンスを発揮しますが、さらに改善するとよいでしょう。

私はLoadAsync()を使用することを検討しました。これは、もう少しリファクタリングが可能になり、残りのアーキテクチャとよりよく適合するでしょう。

ただし、dbコンテキストでは一度に1つのクエリしか実行できません。そのため、新しいdbコンテキストを作成し、ナビゲーションプロパティの各グループに対してLoadAsync()を(非同期に)実行してから、すべての結果を連結できるかどうか疑問に思っていました。

新しいコンテキストを作成し、ナビゲーショングループごとにLoadAsync()を起動する方法は技術的にわかっていますが、結果を連結する方法はわかりません。それが確実に可能かどうか、またはそれがうまくいくかどうかはわかりません練習。

だから私の質問です。これは可能ですか、またはパフォーマンスをさらに向上させることができる別の方法はありますか?いくつかのストアドプロシージャを作成するのではなく、Entity Frameworkが提供するものに固執しようとしています。ありがとう

[〜#〜]更新[〜#〜]

すべてのインクルードを1つのステートメントで使用することと、これらを小さなグループでロードすることの間に見られるパフォーマンスの違いについて。 6000アイテムを返すクエリを実行する場合。 (SQLプロファイラーとVS診断を使用して時間を決定する)

グループ化されたインクルード:インクルードの実行に合計で最大8秒かかります。

1つのステートメントに含まれる:SQLクエリの読み込みに最大30秒かかります。 (多くの場合、タイムアウトになります)

もう少し調査した結果、EFがsqlの結果をモデルに変換するときにオーバーヘッドはそれほどないと思います。ただし、EFが複雑なクエリを生成するのに500ミリ秒近くかかることはありましたが、これは理想的ではありませんが、これを解決できるかどうかはわかりません。

更新2

Ivanの助けを得て、これに従って https://msdn.Microsoft.com/en-gb/data/hh949853.aspx 特にSelectManyを使用して、さらに改善することができました。 EFのパフォーマンスを改善しようとしている人には、msdnの記事を強くお勧めします。

17
Corporalis

2番目のアプローチは、EFナビゲーションプロパティの修正プロセスに依存しています。問題は、

query.Include(q => q.ItemNavN).Load();

ステートメントには、関連するエンティティデータとともにすべてのマスターレコードデータも含まれます。

同じ基本的な考え方を使用すると、ナビゲーションプロパティごとに1つのLoadを実行し、IncludeSelect(参照用)またはSelectMany(コレクション用)-EF CoreがIncludesを内部的に処理する方法に似たもの。

2番目のアプローチの例をとると、以下を試してパフォーマンスを比較できます。

var query = ctx.Filters.Where(x => x.SessionId == id)
    .Join(ctx.Items, i => i.ItemId, fs => fs.Id, (f, fs) => fs);

query.Select(x => x.ItemNav1).Load();
query.Select(x => x.ItemNav2).Load();
query.Select(x => x.ItemNav3).Load();
query.Select(x => x.ItemNav4).Load();
query.Select(x => x.ItemNav5).Load();
query.Select(x => x.ItemNav6).Load();

var result = query.ToList();
// here all the navigation properties should be populated 
12
Ivan Stoev

ここに来るみんなのために、私はあなたに次の2つのことを知ってほしい:

  1. .Select(x => x.NavProp).Load()は、トラッキングをオフにしている場合、実際にはナビゲーションプロパティをロードしません。

  2. バージョン3.0.0以降、各インクルードにより、リレーショナルプロバイダーによって生成されたSQLクエリに追加のJOINが追加されますが、以前のバージョンでは追加のSQLクエリが生成されました。これにより、クエリのパフォーマンスが大幅に変化する可能性があります。特に、包含演算子の数が非常に多いLINQクエリは、デカルト爆発の問題を回避するために、複数の個別のLINQクエリに分割する必要がある場合があります。

両方のステートメントのソース: https://docs.Microsoft.com/en-us/ef/core/querying/related-data

したがって、EF CoreがバックグラウンドでSelectとSelectManyを実行することは正しくありません。私の場合、ナビゲーションプロパティのロードを含む単一のエンティティがあり、インクルードでは実際に15,000行を超えてロードされていました(そう、それは正解で、デカルト爆発の問題と呼ばれます)。 Select/SelectManyで動作するようにコードをリファクタリングした後、その行数は118に減少しました。クエリ時間は4秒から1秒未満に短縮されました(たとえ20が正確に含まれていても)

これが誰かの役に立つことを願っています。そしてIvanに感謝します。

2
Zoran P.

これは技術的に2つのクエリを実行することを知っていますが、10,000アイテムが格納されていて、10アイテムのみを返したい場合は、それらの10アイテムのナビゲーションプロパティのみを含める方が理にかなっています。

.Include演算子の動作を誤解していると思います。次のコードでは、DBは必要なアイテムのみを返し、「追加データ」はありません。

ctx.Items.Include(e => e.ItemNav1)
         .Include(e => e.ItemNav2)
         .Include(e => e.ItemNav3)
         .Include(e => e.ItemNav4)
         .Include(e => e.ItemNav5)
         .Include(e => e.ItemNav6)
         .Where(<filter criteria>)
         .ToList();

フィルター条件に一致するアイテムが10個しかない場合、これらのアイテムのデータのみが返されます。裏側では、.IncludeはSQL JOINとほぼ同じです。パフォーマンスに関する考慮事項はまだありますが、この標準構文を回避する理由は(私が知っていることですが)本当にありません。


結合によってパフォーマンスの問題が発生している場合は、データベースに問題がある可能性があります。適切なインデックスはありますか?それらは断片化されていますか?

1
Vlad274