Генерация SQL EF6 для <где нулевые столбцы равны>
Пытаясь перейти с EF5 на EF6, я столкнулся со значимой таблицей поиска разрыва производительности с помощью столбца с нулевым значением. Вот пример:
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, он добавляет некоторую логику для нулевой обработки:
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 создал более простой SQL под EF5:
SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[ManagerId] = @p__linq__0
Я могу понять, что разработчики пытались достичь: если вы укажете null как параметр, запрос, где managerId = null, не будет выбирать строки. Я ценю заботу, но логика поиска в 99,9% разделяется: один пользовательский поиск ищет where ManagerId == null
, другой ищет определенный id where ManagerId == managerId
Проблема заключается в большом влиянии на производительность: MS SQL не использует индекс для ManagerId и происходит сканирование таблицы. Мой проект имеет сотни похожих поисков и размер базы данных около 100 ГБ общей производительности после обновления до EF6, сокращенного на 10 примерно.
Вопрос в том, кто-нибудь знает какую-то конфигурацию или соглашение, чтобы отключить этот roadblock в EF6 и создать простой sql?
EDIT:
Я проверил дюжину похожих элементов в моем проекте и обнаружил, что:
- В некоторых случаях SQL SERVER использует индекс, указанный для поля I
поиск. И даже в этом случае наблюдается небольшая потеря производительности: это
использует индекс дважды: сначала ищет значение, указанное в
параметр, второй раз ищет нуль
-
EF6 даже проверяет значение 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)
. Вторая часть всегда ложна, но добавление этой части дает индекс.
Мой рутинный импорт около 1,7 млн записей (что обычно занимал около 30 минут) составляет 3 часа, а прогресс составляет около 30%.
Ответы
Ответ 1
Установите
db.Configuration.UseDatabaseNullSemantics = true;
чтобы получить поведение, которое вы имели в EF5. Этот workitem описывает, какова разница между true
и false
и должна помочь вам решить, согласны ли вы со старым поведением или нет.
Ответ 2
Очень отличный ответ.
Если вы используете varchar (xxx), LNQ to SQL выплевывает nvarchar (4000), который ломает индексы и конверсии, массивно выдувающие ваш sql-план. В моем случае я нашел этот вопрос из-за поведения с нечетным нулем, но это не было проблемой. В ответе ниже рассматриваются как null, так и nvarchar. SQL-план пошел от ~ 11 до .006.
public class InterestingRow
{
[Key]
public int interesting_row_id { get; set; }
[StringLength(255), Required, Column(TypeName = "varchar")]
public string public_guid { get; set; }
}
(Да, есть много причин использовать varchar, например, вы храните публично открытый guid)