sql >> データベース >  >> RDS >> Sqlserver

Include()を何度も使用すると、Entity-Frameworkコードが遅くなります

    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つのIncludeRoot + 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関係。



    1. SQLフルテキストインデックスの作成がいつ終了したかを知るにはどうすればよいですか?

    2. MySQLでのINSTR()関数のしくみ

    3. SQLCipherとgreenDAOの統合

    4. 適切なAzureVMサイズを選択することの重要性