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

12

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

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

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

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

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

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

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

План выполнения Query1 План выполнения Query2

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

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

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

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

введите описание изображения здесь

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

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 записей

Рейчел
источник

Ответы:

8

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

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

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

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

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

чтобы предотвратить использование этого конкретного плана.

Мартин Смит
источник
2

Можем ли мы контролировать 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 вы можете просто создать представление и использовать подсказку WITH (NOEXPAND).

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

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
ooutwire
источник
MAXDOPустанавливает максимальную степень параллелизма, которая ограничивает число процессоров, которые может использовать запрос. Это в основном заставит второй запрос работать так же медленно, как и первый, поскольку он удаляет возможности использовать параллелизм, а это не то, что мне нужно.
Рэйчел
@Рэйчел, я согласна; но мы не можем ничего сравнивать, если не установим некоторые основные правила. Я не могу легко сравнить параллельный процесс, работающий на 64 ядрах, с одним потоком, работающим на одном. В конце концов, я надеюсь, что все наши машины имеют хотя бы один логический процессор = -)
ooutwire
0

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

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

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

Йоэль Хэлб
источник