私はいくつかの遅いコードをデバッグしていますが、犯人は以下に掲載されているEFコードであるようです。クエリが後の段階で評価される場合、4〜5秒かかります。 1秒未満で実行できるようにしようとしています。
SQL Server Profilerを使用してこれをテストしましたが、多くのSQLスクリプトが実行されているようです。また、SQLサーバーが実行を完了するまでに3〜4秒かかることを確認します。
Include()の使用に関する他の同様の質問を読みましたが、使用するとパフォーマンスが低下するようです。以下のコードをいくつかの異なるクエリに分割しようとしましたが、それほど違いはありません。
以下を高速に実行する方法はありますか?
現在、私が取り組んでいるWebアプリは、以下が完了するのを待っている間、空のiframeを表示しているだけです。実行時間を短縮できない場合は、分割して、iframeにデータを部分的に読み込むか、別の非同期ソリューションを使用する必要があります。ここでのアイデアも歓迎します!
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
{
formInstance = context.FormInstanceSet
.Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections))
.Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions))
.Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames))
.Include(x => x.CurrentFormStateInstance)
.Include(x => x.Files)
.FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier);
scope.Complete();
}
インクルードを使用するとパフォーマンスが低下するようです
それは控えめな表現です!複数のInclude
sは、幅と長さの両方でSQLクエリの結果をすばやく爆発させます。何故ですか?
tl; dr複数のInclude
sがSQL結果セットを爆破します。すぐに、1つのメガステートメントを実行する代わりに、複数のデータベース呼び出しでデータをロードする方が安くなります。 Include
とLoad
ステートメントの最適な組み合わせを見つけてください。
Include
sの成長因子私たちが持っているとしましょう
Root
Root.Parent
_Root.Children1
_および_Root.Children2
_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
句のcolumnsの数は、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
を使用することは、データベース呼び出しのコストとデータ量の微妙なバランスです。経験則を与えるのは難しいですが、今では、子コレクションに〜3 Includes
を超える(ただしかなり多い場合)ことで、データ量が通常、追加の呼び出しのコストをすぐに上回ると想像できます親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はrelationship fixupを実行します。これにより、ロードされたエンティティによってナビゲーションプロパティ(_Root.Children
_など)が自動入力されます。最終結果は、1つの重要な違いを除いて、Include
sを含むステートメントと同じです。子コレクションは、エンティティ状態マネージャーでロード済みとしてマークされないため、EFは、アクセス時に遅延ロードをトリガーしようとします。そのため、遅延読み込みをオフにすることが重要です。
実際には、Include
およびLoad
ステートメントのどの組み合わせが最適かを把握する必要があります。
Include
ごとにクエリの複雑さも増すため、データベースのクエリオプティマイザーは、最適なクエリプランを見つけるためにますます努力する必要があります。ある時点で、これはもはや成功しないかもしれません。また、いくつかの重要なインデックスが欠落している場合(特に外部キーで)、Include
sを追加することにより、最適なプランクエリパフォーマンスでさえも低下する可能性があります。
15以上の "Include"ステートメントがあり、2M +行の結果を7分で生成したクエリでも同様の問題が発生しました。
私のために働いた解決策は次のとおりでした:
サンプルは次のとおりです。
public IQueryable<CustomObject> PerformQuery(int id)
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.AutoDetectChangesEnabled = false;
IQueryable<CustomObject> customObjectQueryable = ctx.CustomObjects.Where(x => x.Id == id);
var selectQuery = customObjectQueryable.Select(x => x.YourObject)
.Include(c => c.YourFirstCollection)
.Include(c => c.YourFirstCollection.OtherCollection)
.Include(c => c.YourSecondCollection);
var otherObjects = customObjectQueryable.SelectMany(x => x.OtherObjects);
selectQuery.FirstOrDefault();
otherObjects.ToList();
return customObjectQueryable;
}
IQueryableは、サーバー側ですべてのフィルタリングを行うために必要です。 IEnumerableはメモリ内でフィルタリングを実行しますが、これは非常に時間のかかるプロセスです。 Entity Frameworkは、メモリ内の関連付けを修正します。
「含める」ことを試みるすべてのエンティティ間の関係を正しく構成しましたか?少なくとも1つのエンティティが他のいくつかのエンティティと関係を持たない場合、EFはSQL結合構文を使用して1つの複雑なクエリを構築できません-代わりに、所有する「インクルード」の数だけクエリを実行します。そしてもちろん、それはパフォーマンスの問題につながります。データを取得するためにEFが生成する正確なクエリ(-es)を投稿してください。