EF5 から EF6 にアップグレードしようとすると、null 許容列でテーブルを検索する際に重大なパフォーマンス ギャップが発生します。以下にサンプルを示します。
public class Customer
{
public int Id { get; set; }
public int? ManagerId { get; set; }
//public virtual Manager Manager { get; set; }
}
public class MyContext : DbContext
{
public MyContext(string connstring): base(connstring){}
public DbSet<Customer> Customers { get; set; }
}
class Program
{
static void Main(string[] args)
{
var db = new MyContext("CONNSTRING");
var managerId = 1234;
var q = from b in db.Customers
where b.ManagerId == managerId
select b.Id;
var s = q.ToString();
}
}
EF6 が SQL を生成するとき、null 処理のためのいくつかのロジックが追加されます。
SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE (([Extent1].[ManagerId] = @p__linq__0)
AND ( NOT ([Extent1].[ManagerId] IS NULL OR @p__linq__0 IS NULL)))
OR (([Extent1].[ManagerId] IS NULL) AND (@p__linq__0 IS NULL))
同じ linq が EF5 でより単純な SQL を生成したことに注意してください。
SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[ManagerId] = @p__linq__0
開発者が達成しようとしたポイントを理解できます。パラメーターとして null を指定すると、managerId = null のクエリは行を選択しません。お気遣いに感謝しますが、99.9% の検索ロジックが分離されています。1 つのユースケースでは がwhere ManagerId == null
検索され、別のユースケースでは特定の ID が検索されます。where ManagerId == managerId
問題はパフォーマンスに大きな影響を与えることです。MS SQL は ManagerId のインデックスを使用せず、テーブル スキャンが発生します。私のプロジェクトには何百もの同様の検索があり、EF6 へのアップグレード後のデータベース サイズは約 100 GB の全体的なパフォーマンスで、約 10 分の 1 になりました。
問題は、EF6 でこの障害を無効にして単純な SQL を生成するための何らかの構成または規則を知っている人はいますか?
編集:
プロジェクトで同様の選択を12個チェックしたところ、次のことがわかりました。
- 場合によっては、SQL SERVER は、検索するフィールドに指定されたインデックスを使用します。この場合でも、わずかなパフォーマンスの低下があります。インデックスを 2 回使用します。1 回目はパラメーターで指定した値を探し、2 回目は null を探します。
EF6 は、定数が null ではないと正確に指定されている場合でも、null をチェックします。次に例を示します。
from p in db.PtnActivations where p.Carrier != "ALLTEL" where p.Carrier != "ATT" where p.Carrier != "VERIZON"
SQL を生成する
WHERE ( NOT (('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('ATT' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)))
それはキャリアの私のインデックスを利用しませんでした。EF5バージョンは持っていた
( NOT (('ALLTEL' = [Extent1].[Carrier]))) AND ( NOT (('ATT' = [Extent1].[Carrier]))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) ))
それを利用したもの。
状態に注意してください('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)
。2 番目の部分は常にfalse ですが、この部分を追加するとインデックスが失われます。
約 170 万件のレコードの定期的なインポート (通常は約 30 分かかります) は 3 時間で完了し、進行状況は約 30% です。