Запит Entity Framework повільний, але той самий SQL у SqlQuery швидкий


93

Я бачу деякі справді дивні можливості, пов’язані з дуже простим запитом за допомогою Entity Framework Code-First з .NET framework версії 4. Запит LINQ2Entities виглядає так:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Для цього потрібно понад 3000 мілісекунд. Створений SQL виглядає дуже просто:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Цей запит виконується майже миттєво під час запуску через Management Studio. Коли я змінюю код C # на використання функції SqlQuery, він працює за 5-10 мілісекунд:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Отже, точно такий же SQL, отримані сутності відстежуються в обох випадках, але дика різниця між ними. Що дає?


2
Я сподіваюся, ви бачите затримки ініціалізації - ймовірно, перегляньте компіляцію. Див. MSDN:Performance Considerations for Entity Framework 5
Ніколас Батлер,

Я пробував попередньо генерувати погляди, і, здається, це не допомагає. Крім того, провів ще один запит EF перед повільним, щоб виключити матеріали ініціалізації. Новий запит працював швидко, повільний все ще працював повільно, навіть незважаючи на те, що під час першого запиту сталася розминка контексту.
Брайан Салліван,

1
@marc_s - Ні, SqlQuery поверне повністю матеріалізований екземпляр сутності з відстеженням змін. Див. Msdn.microsoft.com/en-us/library/…
Брайан Салліван,

Створений SQL для вашого запиту EF насправді містить значення параметра або використовує параметр? Це не повинно впливати на швидкість запиту для окремого запиту, але може спричинити здуття плану запитів на сервері з часом.
Джим Вулі,

Ви пробували запускати один і той же запит двічі / кілька разів? Скільки часу знадобилося, коли ви бігали вдруге? Ви пробували це на .NET Framework 4.5 - є деякі вдосконалення, пов'язані з EF, у .NET Framework 4.5, які можуть допомогти.
Павел,

Відповіді:


96

Знайшов це. Виявляється, це питання типів даних SQL. SomeStringPropСтовпець в базі даних була VARCHAR, але EF передбачає , що рядкові типи .NET є nvarchars. Отриманий процес перекладу під час запиту для БД для порівняння - це те, що займає багато часу. Я думаю, що EF Prof трохи звів мене зі шляху, точнішим поданням виконуваного запиту було б наступне:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Отже, отримане виправлення полягає в коментуванні першої кодової моделі із зазначенням правильного типу даних SQL:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}

1
Гарне розслідування. Ваш запит страждав від "неявного перетворення", як це пояснюється тут: brentozar.com/archive/2012/07/…
Хайме,

Врятував мене кілька годин налагодження. Саме в цьому була проблема.
Коді

1
У моєму випадку я використовую EDMX із застарілою базою даних, яка використовує varcharвсе, і справді в цьому була проблема. Цікаво, чи можу я зробити EDMX, щоб розглянути varchar для всього стовпця рядка.
Аліссон,

1
Чудова знахідка людини. але @Jaime те, що нам слід зробити для першого підходу до бази даних, оскільки все (наприклад, анотація даних на моделях db) стирається після оновлення моделі EF з бази даних.
Науман-хан

Встановивши це на деякий час як свою домашню сторінку, щоб я міг пережити хвилювання від знаходження такої чудової відповіді ще на деякий час. Дякую!!!
OJisBad,

43

Причиною уповільнення моїх запитів, зроблених у EF, було порівняння сканованих скалярів, що не допускають обнулення, та скалярів, що дозволяють обнуляти:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Цей запит зайняв 35 секунд. Але крихітний такий рефакторинг:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

дає неймовірні результати. На завершення пішло лише 50 мс. Можливо, це помилка в EF.


13
Це так дивно
Даніель Карденас

1
О БОЖЕ МІЙ. Мабуть, це може трапитися і при використанні інтерфейсів IUserId.Id спричиняв проблему зі мною, але спочатку зіставлення Id до цілого числа працює ... чи потрібно мені перевіряти всі запити в моєму додатку на 100 000 рядків?
Дірк Бур,

чи повідомлялося про цю помилку? Це все ще в останній версії 6.2.0
Дірк Бур,

2
Те саме питання також у EF Core. Дякуємо, що знайшли це!
Yannickv

Ще однією пропозицією є обробка змінної перед введенням у вираз LINQ. Інакше створений sql буде набагато довшим і повільнішим. Я відчував, коли Trim () і ToLower () всередині виразу LINQ викликали помилку.
samheihey


4

У мене була та ж проблема (запит швидкий при виконанні з менеджера SQL), але при виконанні з EF час очікування закінчується.

Виявляється, сутність (яка була створена з подання) мала неправильні ключі сутності. Отже, сутність мала повторювані рядки з однаковими ключами, і, мабуть, для цього потрібно було групувати на задньому плані.


3

Я також зіткнувся з цим із складним запитом ef. Одним із виправлень для мене, який зменшив 6-секундний запит ef до додаткового другого запиту sql, який він створив, було вимкнення лінивого завантаження.

Щоб знайти це налаштування (ef 6), перейдіть до файлу .edmx і загляньте у Властивості -> Генерація коду -> Увімкнено ліниве завантаження. Встановлено значення false.

Значне покращення продуктивності для мене.


4
Це круто, але не має нічого спільного з питанням про плакати.
Jace Rhea

2

У мене була така проблема. Виявляється, винуватцем у моєму випадку було обнюхування параметрів SQL-сервера .

Перший підказка про те, що моя проблема насправді була пов'язана з нюханням параметрів, полягала в тому, що запуск запиту з "вимкнути arithabort вимкнено" або "встановити arithabort увімкнено" призвів до кардинально різного часу виконання в Management Studio. Це тому, що ADO.NET за замовчуванням використовує "вимкнути arithabort", а за замовчуванням Management Studio має "встановити arithabort". Кеш плану запитів зберігає різні плани залежно від цього параметра.

Я вимкнув кешування плану запитів для запиту, рішення можна знайти тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.