Почему 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. Я не хочу полностью переписывать прикладной уровень.
Ответы
Ответ 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, но все же уродливы.
Однако, возможно, вы, возможно, создадите нужный план выполнения, и запрос будет работать плавно.