17

最近非常に遅くなったコードをリファクタリングしようとしていて、実行に 5 秒以上かかるコード ブロックに遭遇しました。

このコードは、次の 2 つのステートメントで構成されています。

IEnumerable<int> StudentIds = _entities.Filters
                    .Where(x => x.TeacherId == Profile.TeacherId.Value && x.StudentId != null)
                    .Select(x => x.StudentId)
                    .Distinct<int>();

_entities.StudentClassrooms
                    .Include("ClassroomTerm.Classroom.School.District")
                    .Include("ClassroomTerm.Teacher.Profile")
                    .Include("Student")
                    .Where(x => StudentIds.Contains(x.StudentId)
                    && x.ClassroomTerm.IsActive
                    && x.ClassroomTerm.Classroom.IsActive
                    && x.ClassroomTerm.Classroom.School.IsActive
                    && x.ClassroomTerm.Classroom.School.District.IsActive).AsQueryable<StudentClassroom>();

少し面倒ですが、最初に 1 つのテーブル (フィルター) から Id の個別のリストを取得し、それを使用して別のテーブルにクエリを実行します。

これらは比較的小さなテーブルですが、それでもクエリ時間は 5 秒以上です。

これをLINQPadに入れたところ、最初に一番下のクエリを実行し、その後1000個の「個別の」クエリを実行していたことがわかりました。

気まぐれで、最後に .ToArray() を追加するだけで「StudentIds」コードを変更しました。これにより、速度が 1000 倍向上しました。同じクエリを完了するのに 100 ミリ秒ほどかかります。

どうしたんだ?私は何を間違っていますか?

4

2 に答える 2

25

これは、Linq での遅延実行の落とし穴の 1 つです。最初のアプローチでは、メモリ内コレクションではなく、StudentIds実際にはです。IQueryableつまり、2 番目のクエリで使用すると、データベースでクエリが再実行されます。

を使用して最初のクエリを強制的に実行するToArray()StudentIds、メモリ内コレクションが作成Containsされ、2 番目のクエリの一部は、アイテムの固定シーケンスを含むこのコレクションに対して実行されます。これは、SQLwhere StudentId in (1,2,3,4)クエリに相当するものにマップされます。

Whereもちろん、このクエリは、句が実行されるたびにではなく、事前にこのシーケンスを決定したため、はるかに高速になります。使用しない 2 番目のクエリToArray()(と思います) は、where exists (...)行ごとに評価されるサブクエリを使用して SQL クエリにマップされます。

于 2012-04-09T22:38:07.397 に答える
4

ToArray()サーバー メモリへの初期クエリを具体化します。

私の推測では、クエリ プロバイダーは式を解析できませんStudentIds.Contains(x.StudentId)。したがって、おそらくstudentIds既にメモリにロードされている配列であると考えられます。そのため、おそらく解析フェーズでデータベースに何度もクエリを実行しています。確実に知る唯一の方法は、プロファイラーをセットアップすることです。

これを db サーバーで行う必要がある場合は、「contains」の代わりに結合を使用してください。結合の問題のように見えることを行うために contains を使用する必要がある場合は、代理主キーまたは外部キーがどこかに欠けている可能性があります。

studentIdsIEnumerable の代わりに IQueryable として宣言することもできます。studentIdsこれにより、クエリ プロバイダーは、 as 式 (別名)を解釈するために必要なヒントを得ることができます。データがまだメモリにロードされていません。私はどういうわけかこれを疑っていますが、試してみる価値があります。

他のすべてが失敗した場合は、を使用しますToArray()。これにより、イニシャルstudentIdsがメモリにロードされます。

于 2012-04-09T22:44:08.273 に答える