Чому сукупний запит значно швидше з пунктом 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 Standard ви можете просто створити подання та використовувати підказку 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

MAXDOPвстановлює максимальний ступінь паралелізму, який обмежує кількість процесорів, які запит може використовувати. Це, в основному, зробить другий запит так само повільним, як і перший, оскільки він видаляє його можливості використовувати паралелізм, який не є тим, що я хочу.
Рейчел

@Rachel Я згоден; але ми не можемо нічого порівняти, якщо не встановимо деякі основні правила. Я не можу легко порівняти паралельний процес, що працює на 64 ядрах, з одним потоком, що працює на одному. Зрештою, я сподіваюся, що всі наші машини мають принаймні один логічний процесор = -)
ooutwire

0

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

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

Таким чином, правильним рішенням може бути спробу різних способів написання запиту, таких як спробу тимчасових таблиць, змінних таблиць, cte, похідних таблиць, параметризації тощо, а також граючи з індексами, індексованими поданнями або відфільтрованими індексами в щоб отримати найкращий план.

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