Подтвердить что ты не робот

Почему Entity Framework генерирует вложенные SQL-запросы?

Почему Entity Framework генерирует вложенные SQL-запросы?

У меня есть этот код

    var db = new Context();
    var result = db.Network.Where(x => x.ServerID == serverId)
        .OrderBy(x=> x.StartTime)
        .Take(limit);

Что порождает это! (Обратите внимание на оператор двойного выбора)

SELECT
`Project1`.`Id`, 
`Project1`.`ServerID`, 
`Project1`.`EventId`, 
`Project1`.`StartTime`
FROM (SELECT
`Extent1`.`Id`, 
`Extent1`.`ServerID`, 
`Extent1`.`EventId`, 
`Extent1`.`StartTime`
FROM `Networkes` AS `Extent1`
 WHERE `Extent1`.`ServerID` = @p__linq__0) AS `Project1`
 ORDER BY 
`Project1`.`StartTime` DESC LIMIT 5

Что следует изменить, чтобы в результате получилось одно предложение select? Я использую MySQL и Entity Framework с кодом First.

Update

У меня тот же результат, независимо от типа параметра, переданного методу OrderBy().

Обновление 2: Сроки

Total Time (hh:mm:ss.ms)    05:34:13.000
Average Time (hh:mm:ss.ms)  25:42.000
Max Time (hh:mm:ss.ms)  51:54.000
Count   13
First Seen  Nov 6, 12 19:48:19
Last Seen   Nov 6, 12 20:40:22

Необработанный запрос:

SELECT `Project?`.`Id`, `Project?`.`ServerID`, `Project?`.`EventId`, `Project?`.`StartTime` FROM (SELECT `Extent?`.`Id`, `Extent?`.`ServerID`, `Extent?`.`EventId`, `Extent?`.`StartTime`, FROM `Network` AS `Extent?` WHERE `Extent?`.`ServerID` = ?) AS `Project?` ORDER BY `Project?`.`Starttime` DESC LIMIT ?

Я использовал программу для выполнения моментальных снимков из текущего процесса в MySQL.

Другие запросы выполнялись одновременно, но когда я изменяю его только на один оператор SELECT, он НИКОГДА не проходит более одной секунды. Возможно, у меня есть что-то еще, что происходит; Я спрашиваю, потому что я не так в БД...

Обновление 3: инструкция объяснения

Создана структура Entity Framework

'1', 'PRIMARY', '<derived2>', 'ALL', NULL, NULL, NULL, NULL, '46', 'Using filesort'
'2', 'DERIVED', 'Extent?', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', '', '45', 'Using where'

Один вкладыш

'1', 'SIMPLE', 'network', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', 'const', '45', 'Using where; Using filesort'

Это из моей среды QA, поэтому время, которое я вставил выше, не связано с операторами объяснения строки. Я думаю, что есть около 500 000 записей, которые соответствуют одному идентификатору сервера.

Решение

Я переключился с MySQL на SQL Server. Я не хочу полностью переписывать прикладной уровень.

4b9b3361

Ответ 1

Это самый простой способ логического построения запроса из дерева выражений. Обычно производительность не будет проблемой. Если у вас проблемы с производительностью, вы можете попробовать что-то вроде этого, чтобы вернуть объекты:

var results = db.ExecuteStoreQuery<Network>(
    "SELECT Id, ServerID, EventId, StartTime FROM Network WHERE ServerID = @ID", 
    serverId);

results = results.OrderBy(x=> x.StartTime).Take(limit);

Ответ 2

Мое первоначальное впечатление заключалось в том, что делать это таким образом было бы фактически более эффективным, хотя при тестировании на сервере MSSQL я получал ответы менее 1 секунды.

С помощью одного оператора select он сортирует все записи (Order By), а затем фильтрует их в набор, который вы хотите видеть (Where), а затем занимает верхнюю пятерку (Limit 5 или, для me, Top 5). На большой таблице сортировка занимает значительную часть времени. С помощью вложенного оператора он сначала фильтрует записи до подмножества, и только после этого на него выполняется дорогостоящая операция сортировки.

Изменить: Я проверил это, но я понял, что у меня ошибка в моем тесте, которая лишила его права. Результаты теста удалены.

Ответ 3

Почему Entity Framework создает вложенный запрос? Простой ответ заключается в том, что Entity Framework разбивает ваше выражение запроса на дерево выражений и затем использует это дерево выражений для построения вашего запроса. Дерево естественно генерирует вложенные выражения запросов (например, дочерний элемент node генерирует запрос, а родительский node генерирует запрос по этому запросу).

Почему платформа Entity Framework не упрощает запрос и не записывает его так, как вам хотелось бы? Простой ответ заключается в том, что в механизм генерации запросов есть ограниченный объем работы, и пока он лучше, чем в предыдущих версиях, он не идеален и, вероятно, никогда не будет.

Все, что говорит, что не должно быть существенной разницы в скорости между запросом, который вы пишете вручную, и созданным в этом случае EF запроса. База данных достаточно умна для создания плана выполнения, который сначала применяет предложение WHERE в любом случае.

Ответ 4

Если вы хотите, чтобы EF генерировал запрос без подзапроса, используйте константу в запросе, а не переменную.

Я ранее создал свой собственный. Где и все другие методы LINQ, которые сначала пересекают дерево выражений и преобразуют все переменные, вызовы методов и т.д. в Expression.Constant. Это было сделано только из-за этой проблемы в Entity Framework...

Ответ 5

Я просто наткнулся на этот пост, потому что у меня такая же проблема. Я уже трачу дни, отслеживая это, и это просто плохое создание запросов в mysql.

Я уже подал ошибку на mysql.com http://bugs.mysql.com/bug.php?id=75272

Подводя итог проблеме:

Этот простой запрос

context.products
    .Include(x => x.category)
    .Take(10)
    .ToList();

переходит в

SELECT
`Limit1`.`C1`, 
`Limit1`.`id`, 
`Limit1`.`name`, 
`Limit1`.`category_id`, 
`Limit1`.`id1`, 
`Limit1`.`name1`
FROM (SELECT
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1`
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id` LIMIT 10) AS `Limit1`

и работает очень хорошо. Во всяком случае, внешний запрос почти бесполезен. Теперь, если я добавлю OrderBy

context.products
    .Include(x => x.category)
    .OrderBy(x => x.id)
    .Take(10)
    .ToList();

запрос изменяется на

SELECT
`Project1`.`C1`, 
`Project1`.`id`, 
`Project1`.`name`, 
`Project1`.`category_id`, 
`Project1`.`id1`, 
`Project1`.`name1`
FROM (SELECT
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1`
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id`) AS `Project1`
 ORDER BY 
`Project1`.`id` ASC LIMIT 10

Это плохо, потому что order by находится во внешнем запросе. Theat означает, что MySQL должен вытащить каждую запись, чтобы выполнить команду orderby, результатом которой является using filesort

Я подтвердил, что SQL Server (как минимум, Comapact) не генерирует вложенные запросы для одного и того же кода

SELECT TOP (10) 
[Extent1].[id] AS [id], 
[Extent1].[name] AS [name], 
[Extent1].[category_id] AS [category_id], 
[Extent2].[id] AS [id1], 
[Extent2].[name] AS [name1], 
FROM  [products] AS [Extent1]
LEFT OUTER JOIN [categories] AS [Extent2] ON [Extent1].[category_id] = [Extent2].[id]
ORDER BY [Extent1].[id] ASC

Ответ 6

На самом деле запросы, созданные Entity Framework, немного уродливы, меньше, чем LINQ 2 SQL, но все же уродливы.

Однако, возможно, вы, возможно, создадите нужный план выполнения, и запрос будет работать плавно.