Почему агрегированный запрос выполняется значительно быстрее с предложением GROUP BY, чем без него?

dba.stackexchange https://dba.stackexchange.com/questions/15295

Вопрос

Мне просто любопытно, почему агрегированный запрос выполняется намного быстрее с GROUP BY оговоркой, чем без таковой.

Например, выполнение этого запроса занимает почти 10 секунд

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

В то время как этот процесс занимает меньше секунды

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate

Есть только один CreatedDate в этом случае, таким образом, сгруппированный запрос возвращает те же результаты, что и негруппированный.

Я заметил, что планы выполнения для двух запросов отличаются - второй запрос использует параллелизм, в то время как первый запрос - нет.

Query1 Execution Plan Query2 Execution Plan

Нормально ли для SQL server по-другому оценивать агрегированный запрос, если в нем нет предложения GROUP BY?И есть ли что-то, что я могу сделать, чтобы улучшить производительность 1-го запроса без использования GROUP BY оговорка?

Редактировать

Я только что узнал, что могу использовать OPTION(querytraceon 8649) установить накладные расходы на параллелизм равными 0, что заставляет запрос использовать некоторый параллелизм и сокращает время выполнения до 2 секунд, хотя я не знаю, есть ли какие-либо недостатки в использовании этой подсказки запроса.

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)

enter image description here

Я бы все же предпочел более короткое время выполнения, поскольку запрос предназначен для заполнения значения при выборе пользователем, поэтому в идеале должен быть мгновенным, как и сгруппированный запрос.Прямо сейчас я просто завершаю свой запрос, но я знаю, что на самом деле это не идеальное решение.

SELECT Min(CreatedDate)
FROM
(
    SELECT Min(CreatedDate) as CreatedDate
    FROM MyTable WITH (NOLOCK) 
    WHERE SomeIndexedValue = 1
    GROUP BY CreatedDate
) as T

Правка №2

В ответ на Запрос Мартина о дополнительной информации:

И то , и другое CreatedDate и SomeIndexedValue имейте для них отдельный неуникальный, некластеризованный индекс. SomeIndexedValue на самом деле это поле varchar(7), хотя в нем хранится числовое значение, которое указывает на PK (int) другой таблицы.Связь между двумя таблицами не определена в базе данных.Я вообще не должен изменять базу данных и могу писать только запросы, которые запрашивают данные.

MyTable содержит более 3 миллионов записей, и каждой записи присваивается группа, к которой она принадлежит (SomeIndexedValue).В группах может быть от 1 до 200 000 записей

Это было полезно?

Решение

Похоже, что он, вероятно, следует за индексом на CreatedDate в порядке от самого низкого к самому высокому и выполнения поисковых запросов для оценки SomeIndexedValue = 1 сказуемое.

Когда он находит первую совпадающую строку, это делается, но вполне возможно, что ему потребуется выполнить гораздо больше поисковых запросов, чем он ожидает, прежде чем он найдет такую строку (предполагается, что строки, соответствующие предикату, случайным образом распределены в соответствии с датой).

Смотрите мой ответ здесь по аналогичному вопросу

Идеальным индексом для этого запроса был бы индекс on SomeIndexedValue, CreatedDate.Предполагая, что вы не можете добавить это или, по крайней мере, создать свой существующий индекс на SomeIndexedValue обложка CreatedDate в качестве включенного столбца вы могли бы попробовать переписать запрос следующим образом

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

чтобы помешать ему использовать этот конкретный план.

Другие советы

Можем ли мы контролировать MaxDop и выбрать известную таблицу, например, AdventureWorks.Production.TransactionHistory?

Когда я повторяю вашу настройку, используя

--#1
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

Затраты идентичны.

Кроме того, я ожидаю (сделайте это) индекс ищет на вашу индексированную ценность; В противном случае вы, вероятно, увидите хэш -матчи вместо агрегатов потока. Вы можете улучшить производительность с помощью не кластеризованных индексов, которые включают значения, которые вы агрегируете, или создаете индексированное представление, которое определяет ваши агрегаты как столбцы. Затем вы будете нажимать на кластерный индекс, который содержит ваши агрегации, индексированным идентификатором. В стандарте SQL вы можете просто создать представление и использовать подсказку с (noexpand).

Пример (я не использую мин, так как он не работает в индексированных представлениях):

USE AdventureWorks ;
GO

-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate) 
INCLUDE (Quantity) ;
GO

-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
    WITH SCHEMABINDING
AS
SELECT 
      TransactionDate 
    , COUNT_BIG(*) AS NumberOfTransactions
    , SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO

CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex 
    ON dbo.SumofQtyByTransDate (TransactionDate) ;  
GO


--#1
SELECT SUM(Quantity) 
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(0))
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

--#3
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO

По моему мнению, причина этой проблемы заключается в том, что оптимизатор SQL Server не ищет лучший план, а не ищет хороший план, как видно из того факта, что после принудительного параллелизма запрос выполнялся гораздо быстрее, что имел оптимизатор не сделано самостоятельно.

Я также видел много ситуаций, когда переписывание запроса в другом формате заключалась в разнице между параллелизацией (например, хотя большинство статей на SQL рекомендуют параметризировать, что я обнаружил, что иногда это вызывает параллелизирование, даже когда параметры, нюхающие, были такими же, как и не. - Параллелизированный один или объединение двух запросов с Союзом, все иногда могут устранить параллелизацию).

Таким образом, правильное решение может заключаться в том, чтобы попробовать различные способы написания запроса, таких как попытка временных таблиц, переменные таблицы, CTE, полученные таблицы, параметризация и т. Д., А также игра с индексами, индексированными представлениями или фильтрованными индексами в Закажите, чтобы получить лучший план.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с dba.stackexchange
scroll top