tl; dr 複数のInclude
■SQL結果セットを爆破します。すぐに、1つのメガステートメントを実行する代わりに、複数のデータベース呼び出しによってデータをロードする方が安価になります。 Include
の最適な組み合わせを見つけてください およびLoad
ステートメント。
インクルードを使用するとパフォーマンスが低下するようです
それは控えめな表現です!複数のInclude
■SQLクエリの結果を幅と長さの両方ですばやく爆破します。なぜですか?
Include
の成長因子 s
(この部分は、Entity Frameworkクラシックv6以前に適用されます)
あるとしましょう
- ルートエンティティ
Root
- 親エンティティ
Root.Parent
- 子エンティティ
Root.Children1
およびRoot.Children2
- LINQステートメント
Root.Include("Parent").Include("Children1").Include("Children2")
これにより、次の構造を持つSQLステートメントが作成されます。
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
これらの<PseudoColumns>
CAST(NULL AS int) AS [C2],
のような式で構成されます そして、それらはすべてのUNION
で同じ量の列を持つように機能します -edクエリ。最初の部分では、Child2
の疑似列を追加します 、2番目の部分では、Child1
の疑似列を追加します 。
これは、SQL結果セットのサイズの意味です:
- 列の数
SELECT
で 句は、4つのテーブルのすべての列の合計です - 行の数 含まれる子コレクションのレコードの合計です
データポイントの総数はcolumns * rows
であるため 、追加の各Include
結果セット内のデータポイントの総数が指数関数的に増加します。 Root
を使用して、そのことを示しましょう 繰り返しになりますが、Children3
が追加されました コレクション。すべてのテーブルに5列と100行がある場合、次のようになります。
1つのInclude
(Root
+ 1つの子コレクション):10列*100行=1000データポイント。
2つのInclude
s(Root
+ 2つの子コレクション):15列*200行=3000データポイント。
3つのInclude
s(Root
+ 3つの子コレクション):20列*300行=6000データポイント。
12個のIncludes
これは78000データポイントになります!
逆に、12個のIncludes
ではなく、各テーブルのすべてのレコードを個別に取得する場合 、13 * 5 * 100
があります データポイント:6500、10%未満!
これらのデータポイントの多くがnull
になるという点で、これらの数値は多少誇張されています。 、したがって、クライアントに送信される結果セットの実際のサイズにはあまり影響しません。ただし、クエリサイズとクエリオプティマイザのタスクは、Include
の数を増やすことで確実に悪影響を受けます。 s。
バランス
したがって、Includes
を使用します データベース呼び出しのコストとデータ量の間の微妙なバランスです。経験則を示すのは難しいですが、Includes
が3つを超えると、データ量が一般的に追加の呼び出しのコストを急速に上回ることが想像できます。 子コレクションの場合(ただし、親のIncludes
の場合はかなり多くなります 、結果セットを広げるだけです。
代替
Include
の代替 個別のクエリでデータを読み込むことです:
context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
これにより、必要なすべてのデータがコンテキストのキャッシュにロードされます。このプロセス中に、EFは関係の修正を実行します ナビゲーションプロパティ(Root.Children
)を自動入力します など)ロードされたエンティティによる。最終結果は、Include
を含むステートメントと同じです。 ■1つの重要な違いを除いて、子コレクションはエンティティ状態マネージャーで読み込み済みとしてマークされていないため、アクセスするとEFは遅延読み込みをトリガーしようとします。そのため、遅延読み込みをオフにすることが重要です。
実際には、Include
のどの組み合わせを把握する必要があります およびLoad
ステートメントはあなたに最適です。
考慮すべきその他の側面
各Include
また、クエリの複雑さが増すため、データベースのクエリオプティマイザは、最適なクエリプランを見つけるためにますます多くの労力を費やす必要があります。ある時点で、これは成功しなくなる可能性があります。また、一部の重要なインデックスが欠落している場合(特に外部キー)、Include
を追加するとパフォーマンスが低下する可能性があります s、最高のクエリプランでも。
EntityFrameworkコア
デカルト爆発
何らかの理由で、上記の動作であるUNIONクエリは、EFコア3で廃止されました。これで、結合を使用して1つのクエリが作成されます。クエリが「スター」型の場合、これはデカルト爆発につながります(SQL結果セット内)。この重大な変更を発表するメモしか見つかりませんが、理由はわかりません。
分割クエリ
このデカルト爆発に対抗するために、Entity Frameworkコア5は、複数のクエリで関連データをロードできるようにする分割クエリの概念を導入しました。これにより、1つの大規模な乗算SQL結果セットを構築できなくなります。また、クエリの複雑さが低いため、複数のラウンドトリップがある場合でもデータのフェッチにかかる時間が短縮される可能性があります。ただし、同時更新が発生すると、データの一貫性が失われる可能性があります。
クエリルートからの複数の1:n関係。